feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
@@ -0,0 +1,7 @@
|
||||
const { defineConfig } = require('@coze-arch/eslint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
packageRoot: __dirname,
|
||||
preset: 'web',
|
||||
rules: {},
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { mergeConfig } from 'vite';
|
||||
import svgr from 'vite-plugin-svgr';
|
||||
|
||||
/** @type { import('@storybook/react-vite').StorybookConfig } */
|
||||
const config = {
|
||||
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.tsx'],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-onboarding',
|
||||
'@storybook/addon-interactions',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: 'tag',
|
||||
},
|
||||
viteFinal: config =>
|
||||
mergeConfig(config, {
|
||||
plugins: [
|
||||
svgr({
|
||||
svgrOptions: {
|
||||
native: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
};
|
||||
export default config;
|
||||
@@ -0,0 +1,14 @@
|
||||
/** @type { import('@storybook/react').Preview } */
|
||||
const preview = {
|
||||
parameters: {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
@@ -0,0 +1,5 @@
|
||||
const { defineConfig } = require('@coze-arch/stylelint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
extends: [],
|
||||
});
|
||||
16
frontend/packages/data/memory/database-v2-main/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# @coze-data/database-v2
|
||||
|
||||
> Project template for react component with storybook.
|
||||
|
||||
## Features
|
||||
|
||||
- [x] eslint & ts
|
||||
- [x] esm bundle
|
||||
- [x] umd bundle
|
||||
- [x] storybook
|
||||
|
||||
## Commands
|
||||
|
||||
- init: `rush update`
|
||||
- dev: `npm run dev`
|
||||
- build: `npm run build`
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"operationSettings": [
|
||||
{
|
||||
"operationName": "test:cov",
|
||||
"outputFolderNames": ["coverage"]
|
||||
},
|
||||
{
|
||||
"operationName": "ts-check",
|
||||
"outputFolderNames": ["./dist"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const { defineConfig } = require('@coze-arch/eslint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
packageRoot: __dirname,
|
||||
preset: 'web',
|
||||
rules: {},
|
||||
});
|
||||
80
frontend/packages/data/memory/database-v2-main/package.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"name": "@coze-data/database-v2",
|
||||
"version": "0.0.1",
|
||||
"description": "database v2",
|
||||
"license": "Apache-2.0",
|
||||
"author": "liushuoyan@bytedance.com",
|
||||
"maintainers": [],
|
||||
"main": "src/index.tsx",
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"lint": "eslint ./ --cache",
|
||||
"test": "vitest --run --passWithNoTests",
|
||||
"test:cov": "npm run test -- --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coze-arch/bot-api": "workspace:*",
|
||||
"@coze-arch/bot-error": "workspace:*",
|
||||
"@coze-arch/bot-http": "workspace:*",
|
||||
"@coze-arch/bot-icons": "workspace:*",
|
||||
"@coze-arch/bot-semi": "workspace:*",
|
||||
"@coze-arch/bot-studio-store": "workspace:*",
|
||||
"@coze-arch/bot-tea": "workspace:*",
|
||||
"@coze-arch/bot-utils": "workspace:*",
|
||||
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
|
||||
"@coze-arch/i18n": "workspace:*",
|
||||
"@coze-arch/report-events": "workspace:*",
|
||||
"@coze-common/table-view": "workspace:*",
|
||||
"@coze-data/database-v2-adapter": "workspace:*",
|
||||
"@coze-data/database-v2-base": "workspace:*",
|
||||
"@coze-data/e2e": "workspace:*",
|
||||
"@coze-data/knowledge-resource-processor-base": "workspace:*",
|
||||
"@coze-data/knowledge-resource-processor-core": "workspace:*",
|
||||
"@coze-data/knowledge-stores": "workspace:*",
|
||||
"@coze-data/reporter": "workspace:*",
|
||||
"@coze-data/utils": "workspace:*",
|
||||
"@coze-studio/bot-detail-store": "workspace:*",
|
||||
"@coze-studio/components": "workspace:*",
|
||||
"@coze-studio/user-store": "workspace:*",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/modifiers": "^7.0.0",
|
||||
"@dnd-kit/sortable": "^7.0.2",
|
||||
"@dnd-kit/utilities": "^3.2.1",
|
||||
"@douyinfe/semi-icons": "^2.36.0",
|
||||
"@douyinfe/semi-illustrations": "^2.36.0",
|
||||
"@douyinfe/semi-ui": "~2.72.3",
|
||||
"ahooks": "^3.7.8",
|
||||
"classnames": "^2.3.2",
|
||||
"date-fns": "^2.23.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"immer": "^10.0.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^4.0.2",
|
||||
"react-router-dom": "^6.11.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coze-arch/bot-typings": "workspace:*",
|
||||
"@coze-arch/eslint-config": "workspace:*",
|
||||
"@coze-arch/stylelint-config": "workspace:*",
|
||||
"@coze-arch/ts-config": "workspace:*",
|
||||
"@coze-arch/vitest-config": "workspace:*",
|
||||
"@testing-library/jest-dom": "^6.1.5",
|
||||
"@testing-library/react": "^14.1.2",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@types/lodash-es": "^4.17.10",
|
||||
"@types/react": "18.2.37",
|
||||
"@types/react-dom": "18.2.15",
|
||||
"@vitest/coverage-v8": "~3.0.5",
|
||||
"react": "~18.2.0",
|
||||
"react-dom": "~18.2.0",
|
||||
"stylelint": "^15.11.0",
|
||||
"typescript": "~5.8.2",
|
||||
"vite-plugin-svgr": "~3.3.0",
|
||||
"vitest": "~3.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.2.0",
|
||||
"react-dom": ">=18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
// 默认值:使用第1个数据表,第1行是表头,第2行开始是数据
|
||||
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 并没有读取 preview_data,但是在判断它非空
|
||||
});
|
||||
if (res.table_meta) {
|
||||
setTableStructure(
|
||||
res.table_meta.map(column => {
|
||||
// 表结构中有同名字段时,使用原本的类型及描述
|
||||
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>({
|
||||
// 第一行文本(文件名)
|
||||
mainText: fileItem.name,
|
||||
// 第二行文本(文件大小)
|
||||
subText: fileSize,
|
||||
// hover 时显示的第二行文本,与上面保持一致
|
||||
tipText: fileSize,
|
||||
// 进度条百分比,初始 10% 与 @coze-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 (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,
|
||||
}));
|
||||
// 进度 100 代表处理完成,更新状态并停止轮询
|
||||
if (data.progress === COMPLETE_PERCENT) {
|
||||
setProgressProps(props => ({
|
||||
...props,
|
||||
status: ProcessStatus.Complete,
|
||||
actions: [I18n.t('datasets_unit_process_success')],
|
||||
}));
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// 提交任务,并开始轮询进度
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
// 需要一个 store,后续改造
|
||||
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 || []}
|
||||
// 测试数据无需控制权限,只要能看到的数据就能修改删除
|
||||
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 -- 历史逻辑
|
||||
NL2DBInfo: NL2DBInfo | null;
|
||||
expertModeConfig?: ExpertModeConfig;
|
||||
onSave?: OnSave;
|
||||
}
|
||||
|
||||
export const DatabaseModal: React.FC<DatabaseModalProps> = props => {
|
||||
const {
|
||||
database,
|
||||
botId,
|
||||
// spaceId,
|
||||
readonly,
|
||||
onCancel,
|
||||
onSave,
|
||||
NL2DBInfo,
|
||||
expertModeConfig,
|
||||
visible,
|
||||
} = props;
|
||||
|
||||
const [generateTableLoading, setGenerateTableLoading] = useState(false);
|
||||
const [contentCheckErrorMsg, setContentCheckErrorMsg] = useState<string>('');
|
||||
const [isEntry, setIsEntry] = useState<boolean>(
|
||||
!database.tableId && !NL2DBInfo,
|
||||
);
|
||||
const [isPreview, setIsPreview] = useState<boolean>(false);
|
||||
const [isDeletedField, setIsDeletedField] = useState<boolean>(false);
|
||||
const [
|
||||
shouldHideDatabaseTableStructureTipsForCurrent,
|
||||
setShouldHideDatabaseTableStructureTipsForCurrent,
|
||||
] = useState<boolean>(false);
|
||||
const [createType, setCreateType] = useState<CreateType>(
|
||||
NL2DBInfo ? CreateType.recommend : CreateType.custom,
|
||||
);
|
||||
const [data, setData] = useState<DatabaseInfo>({
|
||||
tableId: '',
|
||||
name: '',
|
||||
desc: '',
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: [],
|
||||
});
|
||||
const [AIPopoverVisible, setAIPopoverVisible] = useState(false);
|
||||
|
||||
const nlTextAreaRef = useRef<HTMLTextAreaElement>();
|
||||
const tableStructureRef = useRef<DatabaseTableStructureRef>();
|
||||
const [
|
||||
mapOfShouldHidingDatabaseTableStructureTips,
|
||||
setMapOfShouldHidingDatabaseTableStructureTips,
|
||||
] = useLocalStorageState<string | undefined>(
|
||||
// FIXME: 此属性名意义不明确,处为了兼容,暂不修改此属性名,但后续需要使用更明确的命名
|
||||
'use-local-storage-state-modify-tips',
|
||||
{
|
||||
defaultValue: '',
|
||||
},
|
||||
);
|
||||
|
||||
const language = getUnReactiveLanguage();
|
||||
const isEdit = Boolean(data.tableId);
|
||||
const [isReadonly, setIsReadonly] = useState(false);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setIsReadonly(true);
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
await tableStructureRef.current.submit();
|
||||
} finally {
|
||||
setIsReadonly(false);
|
||||
}
|
||||
};
|
||||
|
||||
const hideTableStructureTips = useMemo(() => {
|
||||
const lsMap = JSON.parse(
|
||||
mapOfShouldHidingDatabaseTableStructureTips || '{}',
|
||||
);
|
||||
|
||||
return (
|
||||
!isEdit ||
|
||||
lsMap?.[botId] ||
|
||||
shouldHideDatabaseTableStructureTipsForCurrent
|
||||
);
|
||||
}, [
|
||||
isEdit,
|
||||
shouldHideDatabaseTableStructureTipsForCurrent,
|
||||
mapOfShouldHidingDatabaseTableStructureTips,
|
||||
]);
|
||||
|
||||
const title = useMemo(() => {
|
||||
if (isEdit) {
|
||||
return I18n.t('db_edit_title');
|
||||
}
|
||||
if (createType === CreateType.excel) {
|
||||
return I18n.t('db_table_0126_011');
|
||||
}
|
||||
return I18n.t('db_add_table_title');
|
||||
}, [isEdit, createType]);
|
||||
|
||||
const showEntry = isEntry && !isEdit && !NL2DBInfo;
|
||||
const shouldShowAIGenerate =
|
||||
/**
|
||||
* 1. 入口不展示
|
||||
* 2. 编辑态不展示
|
||||
* 3. Excel导入时不展示
|
||||
*/
|
||||
!showEntry && !isEdit && createType !== CreateType.excel;
|
||||
|
||||
const setDataToDefault = () => {
|
||||
setData({
|
||||
name: '',
|
||||
desc: '',
|
||||
tableId: '',
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: [
|
||||
{
|
||||
nanoid: nanoid(),
|
||||
name: '',
|
||||
desc: '',
|
||||
type: FieldItemType.Text,
|
||||
must_required: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const setTableFieldsListToDefault = () => {
|
||||
tableStructureRef.current?.setTableFieldsList([
|
||||
{
|
||||
nanoid: nanoid(),
|
||||
name: '',
|
||||
desc: '',
|
||||
type: FieldItemType.Text,
|
||||
must_required: false,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const onUseTemplate = () => {
|
||||
setCreateType(CreateType.template);
|
||||
setIsEntry(false);
|
||||
setData({
|
||||
...TEMPLATE_INFO,
|
||||
});
|
||||
};
|
||||
|
||||
const onUseCustom = () => {
|
||||
setCreateType(CreateType.custom);
|
||||
setIsEntry(false);
|
||||
setDataToDefault();
|
||||
};
|
||||
|
||||
const generateTableByNL = async (text: string, type: SceneType) => {
|
||||
setGenerateTableLoading(true);
|
||||
let res: RecommendDataModelResponse | undefined;
|
||||
try {
|
||||
res = await MemoryApi.RecommendDataModel({
|
||||
bot_id: botId,
|
||||
scene_type: type,
|
||||
text,
|
||||
});
|
||||
} catch (error) {
|
||||
setGenerateTableLoading(false);
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseNL2DB,
|
||||
error: error as Error,
|
||||
});
|
||||
setDataToDefault();
|
||||
setTableFieldsListToDefault();
|
||||
}
|
||||
|
||||
if (res?.bot_table_list?.[0]) {
|
||||
if (type === SceneType.BotPersona) {
|
||||
setCreateType(CreateType.recommend);
|
||||
}
|
||||
if (type === SceneType.ModelDesc) {
|
||||
setCreateType(CreateType.naturalLanguage);
|
||||
}
|
||||
setData({
|
||||
tableId: '',
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
name: res.bot_table_list[0].table_name,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
desc: res.bot_table_list[0].table_desc,
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableMemoryList: res.bot_table_list[0].field_list.map(i => ({
|
||||
name: i.name,
|
||||
desc: i.desc,
|
||||
must_required: i.must_required,
|
||||
type: i.type,
|
||||
nanoid: nanoid(),
|
||||
id: Number(i.id),
|
||||
})),
|
||||
});
|
||||
|
||||
// data 是初始值,此处需要手动 setState 更新子组件状态
|
||||
// 若 Modal 已提前关闭,子组件卸载,则 ref 为空,需要加上可选链判断一下
|
||||
tableStructureRef.current?.setTableFieldsList(
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
res.bot_table_list[0].field_list.map(i => ({
|
||||
name: i.name,
|
||||
desc: i.desc,
|
||||
must_required: i.must_required,
|
||||
type: i.type,
|
||||
nanoid: nanoid(),
|
||||
id: Number(i.id),
|
||||
})),
|
||||
);
|
||||
} else {
|
||||
if (type === SceneType.BotPersona) {
|
||||
Toast.info(I18n.t('recommended_failed'));
|
||||
setDataToDefault();
|
||||
setTableFieldsListToDefault();
|
||||
}
|
||||
if (type === SceneType.ModelDesc) {
|
||||
Toast.warning(I18n.t('generate_failed'));
|
||||
setAIPopoverVisible(true);
|
||||
}
|
||||
}
|
||||
setGenerateTableLoading(false);
|
||||
};
|
||||
|
||||
const handleGenerate = () => {
|
||||
const generate = () => {
|
||||
const { value } = nlTextAreaRef.current || {};
|
||||
if (value) {
|
||||
generateTableByNL(value, SceneType.ModelDesc);
|
||||
}
|
||||
};
|
||||
|
||||
sendTeaEvent(EVENT_NAMES.generate_with_ai_click, {
|
||||
bot_id: botId,
|
||||
need_login: true,
|
||||
have_access: true,
|
||||
});
|
||||
setAIPopoverVisible(false);
|
||||
|
||||
if (
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableStructureRef.current.tableFieldsList.filter(i => i.name).length > 0
|
||||
) {
|
||||
Modal.warning({
|
||||
title: I18n.t('bot_database_ai_replace'),
|
||||
content: I18n.t('bot_database_ai_replace_detailed'),
|
||||
okButtonProps: {
|
||||
type: 'warning',
|
||||
},
|
||||
onOk: () => {
|
||||
generate();
|
||||
},
|
||||
maskClosable: false,
|
||||
icon: <IconWarningSize24 />,
|
||||
});
|
||||
} else {
|
||||
generate();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setAIPopoverVisible(false);
|
||||
setIsPreview(false);
|
||||
setShouldHideDatabaseTableStructureTipsForCurrent(false);
|
||||
setIsDeletedField(false);
|
||||
setContentCheckErrorMsg('');
|
||||
setIsEntry(true);
|
||||
}, [visible]);
|
||||
|
||||
useEffect(() => {
|
||||
setData(database);
|
||||
}, [database]);
|
||||
|
||||
useEffect(() => {
|
||||
setCreateType(NL2DBInfo ? CreateType.recommend : CreateType.custom);
|
||||
}, [NL2DBInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (NL2DBInfo && visible) {
|
||||
generateTableByNL(NL2DBInfo.prompt, SceneType.BotPersona);
|
||||
}
|
||||
}, [NL2DBInfo, visible]);
|
||||
|
||||
const DefaultFooter = (
|
||||
<>
|
||||
{contentCheckErrorMsg ? (
|
||||
<Form.ErrorMessage error={contentCheckErrorMsg} />
|
||||
) : null}
|
||||
{hideTableStructureTips ? null : (
|
||||
<div className={s['modal-modify-tips']}>
|
||||
<div className={s.description}>
|
||||
<IconAlertTriangle className={s['tip-icon']} />
|
||||
<span style={{ textAlign: 'left' }}>{I18n.t('db_edit_tips1')}</span>
|
||||
<span
|
||||
className={s.link}
|
||||
onClick={() => {
|
||||
const lsMap = JSON.parse(
|
||||
mapOfShouldHidingDatabaseTableStructureTips || '{}',
|
||||
);
|
||||
lsMap[botId] = true;
|
||||
setMapOfShouldHidingDatabaseTableStructureTips(
|
||||
JSON.stringify(lsMap),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{I18n.t('db_edit_tips2')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<IconClose
|
||||
onClick={() =>
|
||||
setShouldHideDatabaseTableStructureTipsForCurrent(true)
|
||||
}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={s['modal-table-btn']}>
|
||||
{isDeletedField ? (
|
||||
<Popconfirm
|
||||
title={I18n.t('db_del_field_confirm_title')}
|
||||
content={I18n.t('db_del_field_confirm_info')}
|
||||
okText={I18n.t('db_del_field_confirm_yes')}
|
||||
cancelText={I18n.t('db_del_field_confirm_no')}
|
||||
okType="danger"
|
||||
onConfirm={handleSave}
|
||||
>
|
||||
<BotDebugButton
|
||||
loading={isReadonly}
|
||||
theme="solid"
|
||||
type="primary"
|
||||
readonly={readonly}
|
||||
>
|
||||
{I18n.t('db_edit_save')}
|
||||
</BotDebugButton>
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<BotDebugButton
|
||||
readonly={readonly}
|
||||
loading={isReadonly}
|
||||
theme="solid"
|
||||
type="primary"
|
||||
onClick={handleSave}
|
||||
>
|
||||
{I18n.t('db_edit_save')}
|
||||
</BotDebugButton>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const Entry = (
|
||||
<div className={s['modal-temp']}>
|
||||
<div className={s.entry}>
|
||||
<div
|
||||
className={s['entry-method']}
|
||||
onClick={onUseCustom}
|
||||
data-testid={BotE2e.BotDatabaseAddModalAddCustomBtn}
|
||||
>
|
||||
<Icon svg={<AddSVG />} className={s['entry-method-icon']} />
|
||||
<span className={s['entry-method-title']}>
|
||||
{I18n.t('db_add_table_cust')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s['modal-temp-right']}>
|
||||
<div
|
||||
className={s['modal-temp-title']}
|
||||
data-testid={BotE2e.BotDatabaseAddModalTemplateTitle}
|
||||
>
|
||||
{I18n.t('db_add_table_temp_title')}
|
||||
</div>
|
||||
<Image
|
||||
className={s['modal-temp-image']}
|
||||
height={201}
|
||||
src={language === 'zh-CN' ? tableTempCN : tableTempEN}
|
||||
/>
|
||||
<div className={s['modal-temp-description']}>
|
||||
💡{I18n.t('db_add_table_temp_tips')}
|
||||
</div>
|
||||
{isPreview ? (
|
||||
<div className={s['modal-temp-preview']}>
|
||||
<div className={s.title}>
|
||||
{I18n.t('db_add_table_temp_preview_tips')}
|
||||
</div>
|
||||
<Image
|
||||
height={239}
|
||||
src={language === 'zh-CN' ? tablePreviewCN : tablePreviewEN}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={s['modal-temp-btn-group']}>
|
||||
<Button
|
||||
data-testid={BotE2e.BotDatabaseAddModalPreviewTemplateBtn}
|
||||
theme="light"
|
||||
type="tertiary"
|
||||
onClick={() => setIsPreview(state => !state)}
|
||||
className={s['modal-temp-btn']}
|
||||
>
|
||||
{I18n.t('db_add_table_temp_preview')}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid={BotE2e.BotDatabaseAddModalUseTemplateBtn}
|
||||
theme="solid"
|
||||
type="primary"
|
||||
onClick={onUseTemplate}
|
||||
className={s['modal-temp-btn']}
|
||||
>
|
||||
{I18n.t('db_add_table_temp_use')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const getFooter = () => {
|
||||
if (showEntry) {
|
||||
return null;
|
||||
}
|
||||
if (createType === CreateType.excel) {
|
||||
return null;
|
||||
}
|
||||
return DefaultFooter;
|
||||
};
|
||||
|
||||
const getContent = () => {
|
||||
if (showEntry) {
|
||||
return Entry;
|
||||
}
|
||||
if (createType === CreateType.excel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s['database-table-structure-container']}>
|
||||
<DatabaseTableStructure
|
||||
data={data}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
ref={tableStructureRef}
|
||||
loading={generateTableLoading}
|
||||
loadingTips={I18n.t('bot_database_ai_waiting')}
|
||||
botId={botId}
|
||||
readAndWriteModeOptions={
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
expertModeConfig.isExpertMode ? 'expert' : 'normal'
|
||||
}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
maxColumnNum={expertModeConfig.maxColumnNum}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
onDeleteField={list => {
|
||||
setIsDeletedField(
|
||||
!database.tableMemoryList.every(i =>
|
||||
// TODO: 当前field id生成规则有问题,故暂时使用 nanoid 替换
|
||||
list.find(j => j.nanoid === i.nanoid),
|
||||
),
|
||||
);
|
||||
}}
|
||||
createType={createType}
|
||||
setContentCheckErrorMsg={setContentCheckErrorMsg}
|
||||
renderModeSelect={params => <FormDatabaseModeSelect {...params} />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
closable={false}
|
||||
width={1138}
|
||||
centered
|
||||
footer={getFooter()}
|
||||
title={
|
||||
<div className={s['title-wrapper']}>
|
||||
<div data-testid={BotE2e.BotDatabaseAddModalTitle}>{title}</div>
|
||||
<div className={s.right}>
|
||||
{shouldShowAIGenerate ? (
|
||||
<Popover
|
||||
trigger="custom"
|
||||
position="bottomRight"
|
||||
content={
|
||||
<div className={s['generate-ai-popover-wrapper']}>
|
||||
<div
|
||||
className={s.title}
|
||||
data-testid={
|
||||
BotE2e.BotDatabaseAddModalTitleCreateAiModalTitle
|
||||
}
|
||||
>
|
||||
{I18n.t('bot_database_ai_create')}
|
||||
</div>
|
||||
<TextArea
|
||||
data-testid={
|
||||
BotE2e.BotDatabaseAddModalTitleCreateAiModalDesc
|
||||
}
|
||||
autosize
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
ref={nlTextAreaRef}
|
||||
rows={1}
|
||||
placeholder={I18n.t('bot_database_ai_create_tip')}
|
||||
className={s['text-area']}
|
||||
/>
|
||||
<div className={s['button-wrapper']}>
|
||||
<UIButton
|
||||
data-testid={
|
||||
BotE2e.BotDatabaseAddModalTitleCreateAiModalCreateBtn
|
||||
}
|
||||
theme="borderless"
|
||||
onClick={handleGenerate}
|
||||
icon={<Icon svg={<GenerateSVG />} />}
|
||||
>
|
||||
{I18n.t('bot_database_ai_generate')}
|
||||
</UIButton>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
keepDOM
|
||||
visible={AIPopoverVisible}
|
||||
onVisibleChange={_v => {
|
||||
setAIPopoverVisible(_v);
|
||||
}}
|
||||
onClickOutSide={() => {
|
||||
setAIPopoverVisible(false);
|
||||
}}
|
||||
className={s.popover}
|
||||
>
|
||||
<UIButton
|
||||
data-testid={BotE2e.BotDatabaseAddModalTitleCreateAiBtn}
|
||||
theme="borderless"
|
||||
icon={
|
||||
AIPopoverVisible ? (
|
||||
<Icon svg={<UpArrowSVG />} />
|
||||
) : (
|
||||
<Icon svg={<DownArrowSvg />} />
|
||||
)
|
||||
}
|
||||
iconPosition="right"
|
||||
onClick={() => {
|
||||
sendTeaEvent(EVENT_NAMES.nl2table_create_table_click, {
|
||||
bot_id: botId,
|
||||
need_login: true,
|
||||
have_access: true,
|
||||
});
|
||||
setAIPopoverVisible(true);
|
||||
}}
|
||||
>
|
||||
{I18n.t('bot_database_ai_create')}
|
||||
</UIButton>
|
||||
</Popover>
|
||||
) : null}
|
||||
<UIButton
|
||||
data-testid={BotE2e.BotDatabaseAddModalTitleCloseIcon}
|
||||
icon={<IconClose />}
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
onClick={onCancel}
|
||||
className={s['modal-close-button']}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
maskClosable={false}
|
||||
>
|
||||
<div className={s['modal-container']}>{getContent()}</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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,260 @@
|
||||
/*
|
||||
* 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 { FieldItemType } from '@coze-arch/bot-api/memory';
|
||||
import { IconCozEdit, IconCozTrashCan } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
type ColumnProps,
|
||||
IconButton,
|
||||
Popconfirm,
|
||||
Space,
|
||||
Typography,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { type TableRow, type TableField, type TableFieldData } from './type';
|
||||
|
||||
export function formatTableStructList(
|
||||
structList: TableMemoryItem[],
|
||||
): TableFieldData[] {
|
||||
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) {
|
||||
// 系统字段
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Table Field 表头数据
|
||||
*/
|
||||
export const getTableColumns = ({
|
||||
fieldList,
|
||||
connectorNames,
|
||||
isReadonlyMode,
|
||||
handleEditRow,
|
||||
handleDeleteRow,
|
||||
}: GetTableColumnsParams) => {
|
||||
const columns: ColumnProps<TableRow>[] = [];
|
||||
|
||||
// 系统字段列
|
||||
columns.push(
|
||||
...SYSTEM_FIELDS.map(item => ({
|
||||
title: () => (
|
||||
<DatabaseFieldTitle
|
||||
field={item.name}
|
||||
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} />
|
||||
),
|
||||
})),
|
||||
);
|
||||
|
||||
// 用户字段列
|
||||
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} />,
|
||||
})),
|
||||
);
|
||||
|
||||
// 操作列
|
||||
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样式 **/
|
||||
.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部分样式 **/
|
||||
.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],
|
||||
// 编辑行时,要带上原始的 connector_id,后端需要判断数据是否来自/目标为“豆包”渠道
|
||||
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) + 表头(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'], {
|
||||
// database 数据表格在 Project IDE 中要使用 coz-bg-max 白色背景
|
||||
[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';
|
||||
|
||||
/**
|
||||
* 拆分自 packages/data/database-v2/src/components/database-table-data/index.tsx
|
||||
* 原本实现基本是从 Semi 文档复制过来的,排序后的数据也没有提交给服务端,PM 似乎也不知道有这个功能,所以 ...
|
||||
* @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 没有导出 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';
|
||||
|
||||
// 期待的数据结构是什么样的?
|
||||
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>[] {
|
||||
// 字段表头内容来自 ../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: {
|
||||
// 表头的高度是 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) {
|
||||
// 单个Date对象
|
||||
formattedValue = format(dValue, 'yyyy-MM-dd HH:mm:ss');
|
||||
} else if (Array.isArray(dValue)) {
|
||||
// Date[] 或 string[]
|
||||
formattedValue = dValue
|
||||
.map(item => {
|
||||
if (item instanceof Date) {
|
||||
return format(item, 'yyyy-MM-dd HH:mm:ss');
|
||||
} else if (typeof item === 'string') {
|
||||
// 假设字符串为有效日期格式
|
||||
return format(new Date(item), 'yyyy-MM-dd HH:mm:ss');
|
||||
}
|
||||
return '';
|
||||
})
|
||||
.join(', '); // 使用逗号分隔不同的日期
|
||||
} else if (typeof dValue === '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,280 @@
|
||||
/*
|
||||
* 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 { FieldItemType, TableType } from '@coze-arch/bot-api/memory';
|
||||
import {
|
||||
CozInputNumber,
|
||||
type DatePickerProps,
|
||||
DatePicker,
|
||||
Form,
|
||||
TextArea,
|
||||
Modal,
|
||||
Select,
|
||||
withField,
|
||||
type CommonFieldProps,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
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 使用 date-fns 格式
|
||||
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 ? (
|
||||
// 只有“线上数据”支持修改“渠道”字段
|
||||
<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"
|
||||
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}
|
||||
/>
|
||||
),
|
||||
// DatabaseFieldTitle 中已经显示 required * 符号
|
||||
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,
|
||||
// 暂时不做分页加载
|
||||
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 判断 scrollRef 是否触底
|
||||
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: 这里需要根据实际做渲染 */}
|
||||
{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';
|
||||
|
||||
/**
|
||||
* 已经迁移的旧渠道 id 到新渠道 id 的映射
|
||||
* key(旧) -> value(新)
|
||||
*/
|
||||
const migratedConnectorIds: Record<string, string | undefined> = {
|
||||
// 微信服务号
|
||||
'10000114': '10000120',
|
||||
// 微信订阅号
|
||||
'10000115': '10000121',
|
||||
};
|
||||
|
||||
export interface ConnectorOption {
|
||||
label: string;
|
||||
value: string;
|
||||
/** 该渠道 id 是否已经迁移 */
|
||||
migrated?: boolean;
|
||||
}
|
||||
|
||||
export interface UseConnectorOptionsParams {
|
||||
/** 是否包含已迁移的旧渠道,默认不包含 */
|
||||
includeMigrated?: boolean;
|
||||
}
|
||||
|
||||
export function useConnectorOptions({
|
||||
includeMigrated = false,
|
||||
}: UseConnectorOptionsParams = {}): ConnectorOption[] {
|
||||
const { space_id } = useParams<DynamicParams>();
|
||||
// 资源库 workflow 页面的 url 上没有 space_id 参数,需要从 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],
|
||||
// 设置缓存 key, 防止重复请求
|
||||
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 -- 复制历史文件
|
||||
} 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) => {
|
||||
// 缓存 Data 数据,用于在事件中获取数据
|
||||
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 绑定数据库需要 draft_id ,其他场景一般只需要用 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 };
|
||||
};
|
||||
25
frontend/packages/data/memory/database-v2-main/src/index.tsx
Normal file
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
17
frontend/packages/data/memory/database-v2-main/src/typings.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/// <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 这里可能存在时区的问题,联调的时候请注意
|
||||
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 -- 符合预期
|
||||
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.
|
||||
*/
|
||||
|
||||
// int64 的最大值和最小值
|
||||
export const INT64_MAX = BigInt('9223372036854775807');
|
||||
export const INT64_MIN = BigInt('-9223372036854775808');
|
||||
|
||||
/**
|
||||
* 检查数值是否在 int64 范围内
|
||||
* @param value - 要检查的字符串
|
||||
* @returns
|
||||
* - 如果是有效的 int64 范围内的整数,返回 true
|
||||
* - 如果无效或超出范围,返回 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 -- 正常业务逻辑
|
||||
} 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;
|
||||
/**
|
||||
* 表格列伸缩时的回调,用于限制伸缩边界
|
||||
* @param column
|
||||
* @returns
|
||||
*/
|
||||
export const resizeFn = (
|
||||
column: ColumnProps<TableRow>,
|
||||
): ColumnProps<TableRow> => {
|
||||
// 多选框/序号列不可伸缩
|
||||
if (column.key === 'column-selection') {
|
||||
return {
|
||||
...column,
|
||||
resizable: false,
|
||||
width: FIXED_COLUMN_WIDTH,
|
||||
};
|
||||
}
|
||||
// 固定列(操作列)不可伸缩
|
||||
if (column.fixed) {
|
||||
return {
|
||||
...column,
|
||||
resizable: false,
|
||||
};
|
||||
}
|
||||
// 其余字段列可伸缩,但需要限制最小宽度
|
||||
return {
|
||||
...column,
|
||||
width:
|
||||
Number(column.width) < MIN_COLUMN_WIDTH
|
||||
? MIN_COLUMN_WIDTH
|
||||
: Number(column.width),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"compilerOptions": {
|
||||
"types": [],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "./dist/tsconfig.build.tsbuildinfo",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../arch/bot-api/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-error/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-http/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-store/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-tea/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-typings/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-utils/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/i18n/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/report-events/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../common/e2e/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../common/reporter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../common/utils/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../components/bot-icons/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../components/bot-semi/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../components/table-view/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/eslint-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/stylelint-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/ts-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/vitest-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../database-v2-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../database-v2-base/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../knowledge/common/stores/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../knowledge/knowledge-resource-processor-base/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../knowledge/knowledge-resource-processor-core/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/components/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/stores/bot-detail/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/user-store/tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
frontend/packages/data/memory/database-v2-main/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"exclude": ["**/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.misc.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"include": ["__tests__", "stories", "vitest.config.ts", "tailwind.config.ts"],
|
||||
"exclude": ["./dist"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"rootDir": "./",
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/globals"],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { defineConfig } from '@coze-arch/vitest-config';
|
||||
|
||||
export default defineConfig({
|
||||
dirname: __dirname,
|
||||
preset: 'web',
|
||||
});
|
||||