feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
18
frontend/packages/data/memory/database/.storybook/main.js
Normal file
18
frontend/packages/data/memory/database/.storybook/main.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/** @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',
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
14
frontend/packages/data/memory/database/.storybook/preview.js
Normal file
14
frontend/packages/data/memory/database/.storybook/preview.js
Normal file
@@ -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;
|
||||
5
frontend/packages/data/memory/database/.stylelintrc.js
Normal file
5
frontend/packages/data/memory/database/.stylelintrc.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const { defineConfig } = require('@coze-arch/stylelint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
extends: [],
|
||||
});
|
||||
16
frontend/packages/data/memory/database/README.md
Normal file
16
frontend/packages/data/memory/database/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# data-base
|
||||
|
||||
> Project template for react component with storybook and supports publish independently.
|
||||
|
||||
## 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,113 @@
|
||||
/*
|
||||
* 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 { useShallow } from 'zustand/react/shallow';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render } from '@testing-library/react';
|
||||
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
|
||||
import { DatabaseDebug } from '../../src/components/database-debug';
|
||||
|
||||
vi.mock('@coze-studio/bot-detail-store/bot-skill', () => ({
|
||||
useBotSkillStore: vi.fn(selector => {
|
||||
if (typeof selector === 'function') {
|
||||
return selector({
|
||||
databaseList: [
|
||||
{
|
||||
id: 'db1',
|
||||
name: 'Test Database',
|
||||
tableId: 'table1',
|
||||
tables: [
|
||||
{
|
||||
id: 'table1',
|
||||
name: 'Test Table',
|
||||
fields: [
|
||||
{
|
||||
id: 'field1',
|
||||
name: 'Test Field',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return {
|
||||
databaseList: [],
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../../src/components/database-debug/multi-table', () => ({
|
||||
default: vi.fn(() => <div>MultiTable</div>),
|
||||
}));
|
||||
|
||||
vi.mock('@coze-studio/bot-detail-store/bot-info', () => ({
|
||||
useBotInfoStore: vi.fn(() => 'test-bot-id'),
|
||||
}));
|
||||
|
||||
// Mock zustand stores
|
||||
vi.mock('@coze-studio/bot-detail-store/bot-skill', () => ({
|
||||
useBotSkillStore: vi.fn(selector => {
|
||||
if (typeof selector === 'function') {
|
||||
return selector({
|
||||
databaseList: [
|
||||
{
|
||||
id: 'db1',
|
||||
name: 'Test Database',
|
||||
tableId: 'table1',
|
||||
tables: [
|
||||
{
|
||||
id: 'table1',
|
||||
name: 'Test Table',
|
||||
fields: [
|
||||
{
|
||||
id: 'field1',
|
||||
name: 'Test Field',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return {
|
||||
databaseList: [],
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@coze-studio/bot-detail-store/bot-info', () => ({
|
||||
useBotInfoStore: vi.fn(() => 'test-bot-id'),
|
||||
}));
|
||||
|
||||
vi.mock('zustand/react/shallow', () => ({
|
||||
useShallow: vi.fn(fn => fn),
|
||||
}));
|
||||
|
||||
describe('DatabaseDebug', () => {
|
||||
it('should use correct store selectors', () => {
|
||||
render(<DatabaseDebug />);
|
||||
expect(useBotInfoStore).toHaveBeenCalled();
|
||||
expect(useBotSkillStore).toHaveBeenCalled();
|
||||
expect(useShallow).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -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 { describe, expect, it } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { type DatabaseList } from '@coze-studio/bot-detail-store';
|
||||
import { BotTableRWMode, FieldItemType } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import MultiTable from '../../src/components/database-debug/multi-table';
|
||||
import s from '../../src/components/database-debug/index.module.less';
|
||||
|
||||
const mockDatabaseList: DatabaseList = [
|
||||
{
|
||||
tableId: 'table1',
|
||||
name: 'Database 1',
|
||||
desc: 'Test database 1',
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: [
|
||||
{
|
||||
nanoid: 'field1',
|
||||
name: 'Field 1',
|
||||
desc: 'Test field 1',
|
||||
must_required: true,
|
||||
type: FieldItemType.Text,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tableId: 'table2',
|
||||
name: 'Database 2',
|
||||
desc: 'Test database 2',
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: [
|
||||
{
|
||||
nanoid: 'field2',
|
||||
name: 'Field 2',
|
||||
desc: 'Test field 2',
|
||||
must_required: true,
|
||||
type: FieldItemType.Number,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
vi.mock('@coze-arch/coze-design', () => ({
|
||||
TabPane: vi.fn(({ children }) => <div>{children}</div>),
|
||||
Tabs: vi.fn(({ children, renderTabBar }) => (
|
||||
<div>
|
||||
{renderTabBar?.({
|
||||
list: mockDatabaseList.map(item => ({
|
||||
tab: item.name,
|
||||
itemKey: item.tableId,
|
||||
})),
|
||||
activeKey: 'table1',
|
||||
onTabClick: vi.fn(),
|
||||
})}
|
||||
{children}
|
||||
</div>
|
||||
)),
|
||||
Typography: {
|
||||
Text: vi.fn(({ children, className, onClick }) => (
|
||||
<div className={className} onClick={onClick}>
|
||||
{children}
|
||||
</div>
|
||||
)),
|
||||
},
|
||||
Divider: vi.fn(({ key }) => <div key={key}>|</div>),
|
||||
}));
|
||||
|
||||
vi.mock('@coze-data/e2e', () => ({
|
||||
BotE2e: {
|
||||
BotDatabaseDebugModalTableNameTab: 'BotDatabaseDebugModalTableNameTab',
|
||||
BotDatabaseDebugModalResetBtn: 'BotDatabaseDebugModalResetBtn',
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../src/components/database-debug/table/reset-btn', () => ({
|
||||
default: vi.fn(() => <div>ResetBtn</div>),
|
||||
}));
|
||||
|
||||
vi.mock('../../src/components/database-debug/table/index', () => ({
|
||||
DataTable: vi.fn(() => <div>DataTable</div>),
|
||||
}));
|
||||
|
||||
// Mock CSS Modules
|
||||
vi.mock('../../src/components/database-debug/index.module.less', () => ({
|
||||
default: {
|
||||
'modal-content-tabs': 'modal-content-tabs',
|
||||
'tab-bar-box': 'tab-bar-box',
|
||||
'tab-bar-item': 'tab-bar-item',
|
||||
'tab-bar-item-active': 'tab-bar-item-active',
|
||||
},
|
||||
}));
|
||||
|
||||
describe('MultiTable', () => {
|
||||
it('should render database tabs', () => {
|
||||
render(<MultiTable botID="test-bot" databaseList={mockDatabaseList} />);
|
||||
|
||||
expect(screen.getByText('Database 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Database 2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render empty state when no databases', () => {
|
||||
render(<MultiTable botID="test-bot" databaseList={[]} />);
|
||||
|
||||
expect(screen.queryByText('Database 1')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should use provided activeDatabaseID', () => {
|
||||
render(
|
||||
<MultiTable
|
||||
botID="test-bot"
|
||||
databaseList={mockDatabaseList}
|
||||
activeDatabaseID="table2"
|
||||
/>,
|
||||
);
|
||||
|
||||
// 第二个数据库应该被激活
|
||||
const tab = screen.getByText('Database 2').closest('div');
|
||||
expect(tab).toHaveClass(s['tab-bar-item']);
|
||||
});
|
||||
|
||||
it('should handle database switching', () => {
|
||||
render(<MultiTable botID="test-bot" databaseList={mockDatabaseList} />);
|
||||
|
||||
// 点击第二个数据库标签
|
||||
fireEvent.click(screen.getByText('Database 2'));
|
||||
|
||||
// 验证切换是否成功
|
||||
const tab = screen.getByText('Database 2').closest('div');
|
||||
expect(tab).toHaveClass(s['tab-bar-item']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"operationSettings": [
|
||||
{
|
||||
"operationName": "test:cov",
|
||||
"outputFolderNames": ["coverage"]
|
||||
},
|
||||
{
|
||||
"operationName": "ts-check",
|
||||
"outputFolderNames": ["./dist"]
|
||||
}
|
||||
]
|
||||
}
|
||||
7
frontend/packages/data/memory/database/eslint.config.js
Normal file
7
frontend/packages/data/memory/database/eslint.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const { defineConfig } = require('@coze-arch/eslint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
packageRoot: __dirname,
|
||||
preset: 'web',
|
||||
rules: {},
|
||||
});
|
||||
109
frontend/packages/data/memory/database/package.json
Normal file
109
frontend/packages/data/memory/database/package.json
Normal file
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"name": "@coze-data/database",
|
||||
"version": "1.0.0",
|
||||
"description": "coze database",
|
||||
"license": "Apache-2.0",
|
||||
"author": "rosefang.123@bytedance.com",
|
||||
"maintainers": [],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./filebox-list": "./src/components/filebox-list/index.tsx",
|
||||
"./multi-table": "./src/components/database-debug/multi-table.tsx",
|
||||
"./table": "./src/components/database-debug/table/index.tsx"
|
||||
},
|
||||
"main": "src/index.ts",
|
||||
"unpkg": "./dist/umd/index.js",
|
||||
"module": "./dist/esm/index.js",
|
||||
"types": "./src/index.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"filebox-list": [
|
||||
"./src/components/filebox-list/index.tsx"
|
||||
],
|
||||
"multi-table": [
|
||||
"./src/components/database-debug/multi-table.tsx"
|
||||
],
|
||||
"table": [
|
||||
"./src/components/database-debug/table/index.tsx"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"lint": "eslint ./ --cache",
|
||||
"test": "vitest --run --passWithNoTests",
|
||||
"test:cov": "npm run test -- --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coze-arch/bot-error": "workspace:*",
|
||||
"@coze-arch/bot-semi": "workspace:*",
|
||||
"@coze-arch/bot-tea": "workspace:*",
|
||||
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
|
||||
"@coze-arch/i18n": "workspace:*",
|
||||
"@coze-common/chat-area-plugin-message-grab": "workspace:*",
|
||||
"@coze-data/e2e": "workspace:*",
|
||||
"@coze-data/knowledge-resource-processor-base": "workspace:*",
|
||||
"@coze-data/knowledge-resource-processor-core": "workspace:*",
|
||||
"@coze-studio/user-store": "workspace:*",
|
||||
"@douyinfe/semi-icons": "^2.36.0",
|
||||
"ahooks": "^3.7.8",
|
||||
"classnames": "^2.3.2",
|
||||
"dayjs": "^1.11.7",
|
||||
"immer": "^10.0.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coze-arch/bot-api": "workspace:*",
|
||||
"@coze-arch/bot-flags": "workspace:*",
|
||||
"@coze-arch/bot-icons": "workspace:*",
|
||||
"@coze-arch/bot-typings": "workspace:*",
|
||||
"@coze-arch/eslint-config": "workspace:*",
|
||||
"@coze-arch/report-events": "workspace:*",
|
||||
"@coze-arch/stylelint-config": "workspace:*",
|
||||
"@coze-arch/ts-config": "workspace:*",
|
||||
"@coze-arch/vitest-config": "workspace:*",
|
||||
"@coze-common/chat-area": "workspace:*",
|
||||
"@coze-common/chat-core": "workspace:*",
|
||||
"@coze-common/table-view": "workspace:*",
|
||||
"@coze-data/reporter": "workspace:*",
|
||||
"@coze-studio/bot-detail-store": "workspace:*",
|
||||
"@douyinfe/semi-illustrations": "^2.36.0",
|
||||
"@rollup/plugin-commonjs": "^24.0.0",
|
||||
"@rollup/plugin-json": "~6.0.0",
|
||||
"@rollup/plugin-node-resolve": "~15.0.1",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@swc/core": "^1.3.35",
|
||||
"@swc/helpers": "^0.4.12",
|
||||
"@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",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"less-loader": "~11.1.3",
|
||||
"postcss": "^8.4.32",
|
||||
"react": "~18.2.0",
|
||||
"react-dom": "~18.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"rollup": "^4.9.0",
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-node-externals": "^6.1.2",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-ts": "^3.1.1",
|
||||
"tailwindcss": "~3.3.3",
|
||||
"vitest": "~3.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.2.0",
|
||||
"react-dom": ">=18.2.0"
|
||||
},
|
||||
"// deps": "immer@^10.0.3 为脚本自动补齐,请勿改动"
|
||||
}
|
||||
|
||||
97
frontend/packages/data/memory/database/rollup.config.mjs
Normal file
97
frontend/packages/data/memory/database/rollup.config.mjs
Normal file
@@ -0,0 +1,97 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import path from 'node:path';
|
||||
import fs from 'fs';
|
||||
|
||||
import tailwindcss from 'tailwindcss';
|
||||
import ts from 'rollup-plugin-ts';
|
||||
import postcss from 'rollup-plugin-postcss';
|
||||
import { nodeExternals } from 'rollup-plugin-node-externals';
|
||||
import cleanup from 'rollup-plugin-cleanup';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import json from '@rollup/plugin-json';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf-8'));
|
||||
|
||||
// CI 环境下不需要构建这么多版本
|
||||
const isInCIEnv = process.env.CI === 'true';
|
||||
|
||||
const banner =
|
||||
'/*!\n' +
|
||||
` * ${packageJson.name} v${packageJson.version}\n` +
|
||||
` * (c) 2023-${new Date().getFullYear()} Flow team\n` +
|
||||
' */';
|
||||
|
||||
const outputConfigs = {
|
||||
esm: {
|
||||
banner,
|
||||
format: 'es',
|
||||
file: path.resolve(__dirname, 'dist/esm/index.js'),
|
||||
},
|
||||
umd: {
|
||||
banner,
|
||||
format: 'umd',
|
||||
file: path.resolve(__dirname, 'dist/umd/index.js'),
|
||||
},
|
||||
};
|
||||
|
||||
const createReplacePlugin = () =>
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
values: {
|
||||
__TEST__: false,
|
||||
__VERSION__: `'${packageJson.version}'`,
|
||||
__DEV__: process.env.NODE_ENV === 'development',
|
||||
},
|
||||
});
|
||||
|
||||
const createConfig = (format, output) => {
|
||||
const isUmdBuild = /^umd/.test(format);
|
||||
const isEsmBuild = /^esm/.test(format);
|
||||
|
||||
const input = path.resolve(__dirname, './src/index.ts');
|
||||
|
||||
// TODO: 这里替换成真实的名称
|
||||
if (isUmdBuild) {
|
||||
output.name = 'FlowFoo';
|
||||
}
|
||||
|
||||
return {
|
||||
input,
|
||||
output,
|
||||
plugins: [
|
||||
commonjs(),
|
||||
createReplacePlugin(),
|
||||
nodeResolve(),
|
||||
cleanup(),
|
||||
postcss({
|
||||
plugins: [tailwindcss(), autoprefixer()],
|
||||
autoModules: true,
|
||||
modules: {
|
||||
generateScopedName: '[name][local]_[hash:base64:5]',
|
||||
},
|
||||
extensions: ['.css', '.less'],
|
||||
}),
|
||||
json({
|
||||
namedExports: false,
|
||||
}),
|
||||
// ESM 仅打包必要内容
|
||||
// UMD 由于需要放到 page 上直接运行,因此需要将所有依赖都打包进来
|
||||
isEsmBuild
|
||||
? nodeExternals({ devDeps: true, peerDeps: true, deps: true })
|
||||
: null,
|
||||
ts({
|
||||
transpiler: 'swc',
|
||||
tsconfig: path.resolve(__dirname, './tsconfig.build.json'),
|
||||
}),
|
||||
].filter(Boolean),
|
||||
};
|
||||
};
|
||||
|
||||
export default Object.keys(outputConfigs)
|
||||
.filter(k => (isInCIEnv ? k === 'esm' : true))
|
||||
.map(format => createConfig(format, outputConfigs[format]));
|
||||
@@ -0,0 +1,50 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
|
||||
.modal-content-tabs {
|
||||
:global {
|
||||
.semi-tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
|
||||
.semi-tabs-content {
|
||||
height: calc(100% - 40px);
|
||||
padding: 0 !important;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.semi-tabs-pane {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.semi-tabs-pane-motion-overlay {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-bar-box {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
padding: 8px 0 8px 16px;
|
||||
}
|
||||
|
||||
.tab-bar-item {
|
||||
cursor: pointer;
|
||||
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 24px;
|
||||
color: var(--Light-usage-text---color-text-2, rgba(29, 28, 35, 60%));
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tab-bar-item-active {
|
||||
color: #4D53E8;
|
||||
}
|
||||
@@ -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 { useShallow } from 'zustand/react/shallow';
|
||||
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
|
||||
import MultiTable from './multi-table';
|
||||
|
||||
export const DatabaseDebug = () => {
|
||||
const botID = useBotInfoStore(state => state.botId);
|
||||
|
||||
const { databaseList } = useBotSkillStore(
|
||||
useShallow(detail => ({
|
||||
databaseList: detail.databaseList,
|
||||
})),
|
||||
);
|
||||
|
||||
return <MultiTable botID={botID} databaseList={databaseList} />;
|
||||
};
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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 { TabPane, Tabs, Typography, Divider } from '@coze-arch/coze-design';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
|
||||
import ResetBtn from './table/reset-btn';
|
||||
import { DataTable, type DataTableRef } from './table';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
import type { DatabaseInfo, DatabaseList } from '@coze-studio/bot-detail-store';
|
||||
|
||||
import {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
const TablePaneContent = forwardRef<
|
||||
DataTableRef,
|
||||
{
|
||||
info: DatabaseInfo;
|
||||
botID?: string;
|
||||
workflowID?: string;
|
||||
projectID?: string;
|
||||
}
|
||||
>(({ info, botID, workflowID, projectID }, ref) => {
|
||||
const _ref = useRef<DataTableRef>(null);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
refetch: _ref.current?.refetch,
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataTable
|
||||
projectID={projectID}
|
||||
database={info}
|
||||
botID={botID}
|
||||
workflowID={workflowID}
|
||||
ref={_ref}
|
||||
/>
|
||||
<div
|
||||
className="absolute top-[6px] right-0"
|
||||
data-testid={BotE2e.BotDatabaseDebugModalResetBtn}
|
||||
>
|
||||
<ResetBtn
|
||||
database={info}
|
||||
botID={botID}
|
||||
workflowID={workflowID}
|
||||
projectID={projectID}
|
||||
afterReset={() => {
|
||||
_ref.current?.refetch?.();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export interface MultiTableProps {
|
||||
botID?: string;
|
||||
workflowID?: string;
|
||||
databaseList: DatabaseList;
|
||||
projectID?: string;
|
||||
activeDatabaseID?: string;
|
||||
}
|
||||
|
||||
const MultiTable = forwardRef<DataTableRef, MultiTableProps>(
|
||||
({ botID, workflowID, databaseList, projectID, activeDatabaseID }, ref) => {
|
||||
const [activeKeyInner, setActiveKeyInner] = useState(
|
||||
activeDatabaseID ?? databaseList?.[0]?.tableId,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof activeDatabaseID !== 'undefined') {
|
||||
setActiveKeyInner(activeDatabaseID);
|
||||
}
|
||||
}, [activeDatabaseID]);
|
||||
|
||||
const tableRefMap = useRef<Record<string, () => Promise<void>>>({});
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
refetch: tableRefMap.current[activeKeyInner],
|
||||
}));
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames({
|
||||
[s['modal-content-tabs']]: true,
|
||||
['h-full']: true,
|
||||
})}
|
||||
>
|
||||
{databaseList.length ? (
|
||||
<Tabs
|
||||
type="line"
|
||||
keepDOM={false}
|
||||
renderTabBar={tabBarProps => {
|
||||
const { list, activeKey, onTabClick } = tabBarProps;
|
||||
return (
|
||||
<div className={classNames([s['tab-bar-box'], 'mr-[108px]'])}>
|
||||
{list?.map((item, index) => (
|
||||
<>
|
||||
<Text
|
||||
data-dtestid={`${BotE2e.BotDatabaseDebugModalTableNameTab}.${item.tab}`}
|
||||
className={classNames({
|
||||
[s['tab-bar-item']]: true,
|
||||
[s['tab-bar-item-active']]:
|
||||
activeKey === item.itemKey,
|
||||
})}
|
||||
onClick={e => {
|
||||
onTabClick?.(item.itemKey, e);
|
||||
}}
|
||||
ellipsis={{
|
||||
showTooltip: true,
|
||||
}}
|
||||
>
|
||||
{item.tab}
|
||||
</Text>
|
||||
{index === list.length - 1 ? null : (
|
||||
<Divider layout="vertical" />
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
activeKey={activeKeyInner}
|
||||
onChange={setActiveKeyInner}
|
||||
>
|
||||
{databaseList.map(item => (
|
||||
<TabPane tab={item.name} itemKey={item.tableId}>
|
||||
<TablePaneContent
|
||||
projectID={projectID}
|
||||
info={item}
|
||||
botID={botID}
|
||||
workflowID={workflowID}
|
||||
ref={tableRef => {
|
||||
if (tableRef?.refetch) {
|
||||
tableRefMap.current[item.tableId] = tableRef.refetch;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</TabPane>
|
||||
))}
|
||||
</Tabs>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default MultiTable;
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash-es';
|
||||
import { type TableMemoryItem } from '@coze-studio/bot-detail-store';
|
||||
import { colWidthCacheService } from '@coze-common/table-view';
|
||||
import { type ColumnProps } from '@coze-arch/bot-semi/Table';
|
||||
import { Typography } from '@coze-arch/bot-semi';
|
||||
import { FieldItemType } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const DEFAULT_WIDTH = 120;
|
||||
export const MAX_WIDTH = 855;
|
||||
|
||||
const getTitle = (name: string, mustRequired: boolean) => (
|
||||
<div className="flex items-center">
|
||||
<Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: { content: name },
|
||||
},
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
{mustRequired ? (
|
||||
<span style={{ color: 'red', height: '16px' }}>*</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
/* eslint-disable complexity */
|
||||
export const getColumns = (
|
||||
_list: TableMemoryItem[],
|
||||
tableId: string,
|
||||
): { list: ColumnProps[]; width: number } => {
|
||||
const cacheWidthMap = colWidthCacheService?.getTableWidthMap(tableId) ?? {};
|
||||
const initWidth =
|
||||
MAX_WIDTH / _list.length > DEFAULT_WIDTH
|
||||
? MAX_WIDTH / _list.length
|
||||
: DEFAULT_WIDTH;
|
||||
const list: ColumnProps[] = _list.map((i, index) => {
|
||||
let res: ColumnProps = {};
|
||||
const width = get(cacheWidthMap, i.name || '');
|
||||
const dataWidth = width ? width : initWidth;
|
||||
const isLast = index === _list.length - 1;
|
||||
switch (i.type) {
|
||||
// 文本
|
||||
case FieldItemType.Text:
|
||||
res = {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
className:
|
||||
isLast && `${styles['last-column-text']} not-resize-handle`,
|
||||
title: getTitle(i.name as string, i.must_required || false),
|
||||
dataIndex: i.name,
|
||||
width: isLast ? undefined : dataWidth,
|
||||
};
|
||||
break;
|
||||
// 整数
|
||||
case FieldItemType.Number:
|
||||
res = {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
className:
|
||||
isLast && `${styles['last-column-min-width']} not-resize-handle`,
|
||||
title: getTitle(i.name as string, i.must_required || false),
|
||||
dataIndex: i.name,
|
||||
width: isLast ? undefined : dataWidth,
|
||||
};
|
||||
break;
|
||||
// 数字
|
||||
case FieldItemType.Float:
|
||||
res = {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
className:
|
||||
isLast && `${styles['last-column-min-width']} not-resize-handle`,
|
||||
title: getTitle(i.name as string, i.must_required || false),
|
||||
dataIndex: i.name,
|
||||
width: isLast ? undefined : dataWidth,
|
||||
};
|
||||
break;
|
||||
// 时间
|
||||
case FieldItemType.Date:
|
||||
res = {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
className:
|
||||
isLast && `${styles['last-column-date']} not-resize-handle`,
|
||||
title: getTitle(i.name as string, i.must_required || false),
|
||||
dataIndex: i.name,
|
||||
width: isLast ? undefined : dataWidth,
|
||||
};
|
||||
break;
|
||||
// 布尔
|
||||
case FieldItemType.Boolean:
|
||||
res = {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
className:
|
||||
isLast && `${styles['last-column-min-width']} not-resize-handle`,
|
||||
title: getTitle(i.name as string, i.must_required || false),
|
||||
dataIndex: i.name,
|
||||
width: isLast ? undefined : dataWidth,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
const defaultWidth = 120;
|
||||
return {
|
||||
list,
|
||||
width: list.reduce(
|
||||
(prev: number, cur: ColumnProps) =>
|
||||
prev + (Number(cur.width) || defaultWidth),
|
||||
0,
|
||||
) as number,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
|
||||
.empty-wrapper-database {
|
||||
:global {
|
||||
.semi-empty-image {
|
||||
align-items: center;
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
.semi-empty-vertical .semi-empty-content {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.data-table {
|
||||
:global {
|
||||
.table-wrapper {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.semi-table-wrapper {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.semi-table-header-sticky .semi-table-thead>.semi-table-row>.semi-table-row-head {
|
||||
background-color: transparent;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.semi-table-tbody>.semi-table-row:hover>.semi-table-row-cell {
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.semi-table-tbody>.semi-table-row>.semi-table-row-cell-ellipsis {
|
||||
overflow: inherit;
|
||||
text-overflow: inherit;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
// 继承高度
|
||||
.semi-spin {
|
||||
position: static;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.semi-spin-children {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.semi-table-small {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.semi-table-container {
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.reset-confirm-modal {
|
||||
:global {
|
||||
.semi-modal-title {
|
||||
color: #1C1D23;
|
||||
}
|
||||
|
||||
.semi-modal-body {
|
||||
color: #1C1D23;
|
||||
}
|
||||
|
||||
.semi-modal-footer .semi-button {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.semi-button-tertiary {
|
||||
color: #1C1D23
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.last-column-min-width {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.last-column-date {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.last-column-text {
|
||||
min-width: 300px;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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 {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { IllustrationConstruction } from '@douyinfe/semi-illustrations';
|
||||
import { type DatabaseInfo } from '@coze-studio/bot-detail-store';
|
||||
import { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import {
|
||||
TableView,
|
||||
type TableViewRecord,
|
||||
colWidthCacheService,
|
||||
} from '@coze-common/table-view';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type ColumnProps } from '@coze-arch/bot-semi/Table';
|
||||
import { UIEmpty } from '@coze-arch/bot-semi';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import {
|
||||
type SearchBotTableInfoResponse,
|
||||
TableType,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { MAX_WIDTH, getColumns } from './get-columns';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface DatabaseTable {
|
||||
database: DatabaseInfo;
|
||||
botID?: string;
|
||||
workflowID?: string;
|
||||
projectID?: string;
|
||||
}
|
||||
|
||||
export interface DataTableRef {
|
||||
refetch?: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const DataTable = forwardRef<DataTableRef, DatabaseTable>(
|
||||
(props, ref) => {
|
||||
const { database, botID, workflowID, projectID } = props;
|
||||
const { tableId } = database;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<Record<string, unknown>[]>([]);
|
||||
|
||||
colWidthCacheService.initWidthMap();
|
||||
const columns: { list: ColumnProps[]; width: number } = useMemo(
|
||||
() => getColumns(database.tableMemoryList, tableId),
|
||||
[database.tableMemoryList],
|
||||
);
|
||||
|
||||
const fetchTableData = async () => {
|
||||
setLoading(true);
|
||||
|
||||
let resp: SearchBotTableInfoResponse | undefined;
|
||||
try {
|
||||
resp = await MemoryApi.ListDatabaseRecords({
|
||||
project_id: projectID,
|
||||
workflow_id: workflowID,
|
||||
bot_id: botID,
|
||||
database_id: tableId,
|
||||
offset: 0,
|
||||
limit: 0,
|
||||
table_type: TableType.DraftTable,
|
||||
});
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseQueryTable,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error
|
||||
: new CustomError(
|
||||
REPORT_EVENTS.DatabaseQueryTable,
|
||||
`${REPORT_EVENTS.DatabaseQueryTable}: operation fail`,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (resp?.data) {
|
||||
setData(resp?.data || []);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTableData();
|
||||
}, []);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
refetch: fetchTableData,
|
||||
}));
|
||||
|
||||
const handleResize = col => {
|
||||
const resizeList = columns.list.filter(
|
||||
item => item.dataIndex !== col.dataIndex,
|
||||
);
|
||||
// 计算拖拽列能拖拽的最小宽度,小于最小宽度则返回最小宽度
|
||||
const widthCount = resizeList.reduce(
|
||||
(prev, cur) => Number(prev) + Number(cur.width),
|
||||
0,
|
||||
);
|
||||
const minWidth = MAX_WIDTH - widthCount;
|
||||
if (widthCount + col.width < MAX_WIDTH) {
|
||||
return {
|
||||
...col,
|
||||
width: col.width < minWidth ? minWidth : col.width,
|
||||
};
|
||||
}
|
||||
return col;
|
||||
};
|
||||
|
||||
if (!data?.length && !loading) {
|
||||
return (
|
||||
<UIEmpty
|
||||
className={classNames([s['empty-wrapper-database'], 'pb-0'])}
|
||||
empty={{
|
||||
icon: <IllustrationConstruction />,
|
||||
title: I18n.t('timecapsule_0108_003'),
|
||||
}}
|
||||
></UIEmpty>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableView
|
||||
tableKey={tableId}
|
||||
columns={columns.list}
|
||||
dataSource={data as TableViewRecord[]}
|
||||
loading={loading}
|
||||
className={s['data-table']}
|
||||
resizable
|
||||
onResize={handleResize}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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 DatabaseInfo } from '@coze-studio/bot-detail-store';
|
||||
import { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { Button, useUIModal } from '@coze-arch/bot-semi';
|
||||
import { IconWarningSize24 } from '@coze-arch/bot-icons';
|
||||
import { TableType } from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface DatabaseTable {
|
||||
database: DatabaseInfo;
|
||||
botID?: string;
|
||||
workflowID?: string;
|
||||
projectID?: string;
|
||||
afterReset?: () => Promise<void> | void;
|
||||
}
|
||||
|
||||
const ResetBtn: React.FC<DatabaseTable> = props => {
|
||||
const { database, botID, workflowID, afterReset, projectID } = props;
|
||||
const { tableId, name } = database;
|
||||
|
||||
const {
|
||||
open,
|
||||
close,
|
||||
modal: clearModal,
|
||||
} = useUIModal({
|
||||
type: 'info',
|
||||
title: I18n.t('dialog_240305_01'),
|
||||
content: I18n.t('dialog_240305_02'),
|
||||
okButtonProps: {
|
||||
type: 'warning',
|
||||
},
|
||||
icon: <IconWarningSize24 />,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await MemoryApi.ResetBotTable({
|
||||
...(workflowID ? { workflow_id: workflowID } : {}),
|
||||
...(botID ? { bot_id: botID } : {}),
|
||||
...(projectID ? { project_id: projectID } : {}),
|
||||
table_id: tableId,
|
||||
table_type: TableType.DraftTable,
|
||||
database_info_id: tableId,
|
||||
});
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
error: error as Error,
|
||||
eventName: REPORT_EVENTS.DatabaseResetTableRecords,
|
||||
});
|
||||
return;
|
||||
}
|
||||
close();
|
||||
|
||||
afterReset?.();
|
||||
},
|
||||
onCancel: () => {
|
||||
close();
|
||||
},
|
||||
className: s['reset-confirm-modal'],
|
||||
// ToolPane的 z-index 是1000,所以此处需要加 1001 的z-index,避免被 database 数据面板遮住
|
||||
zIndex: 1001,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="tertiary"
|
||||
onClick={() => {
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
bot_id: botID ?? '',
|
||||
resource_type: 'database',
|
||||
resource_id: tableId,
|
||||
resource_name: name,
|
||||
action: 'reset',
|
||||
source: 'bot_detail_page',
|
||||
source_detail: 'memory_preview',
|
||||
});
|
||||
open();
|
||||
}}
|
||||
className={s['button-reset']}
|
||||
>
|
||||
{I18n.t('database_240227_01')}
|
||||
</Button>
|
||||
{clearModal(<>{I18n.t('dialog_240305_02')}</>)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetBtn;
|
||||
@@ -0,0 +1,17 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.action-button {
|
||||
color: rgba(29, 28, 35, 60%) !important
|
||||
}
|
||||
|
||||
.rename-form{
|
||||
:global{
|
||||
// 不需要 Form 默认 的padding 间距
|
||||
.semi-form-field{
|
||||
padding: 0;
|
||||
}
|
||||
// 不需要 Form 默认的校验 icon
|
||||
.semi-form-field-validate-status-icon{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
import { useRef, type FC } from 'react';
|
||||
|
||||
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
|
||||
import { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import {
|
||||
GrabElementType,
|
||||
PublicEventNames,
|
||||
publicEventCenter,
|
||||
} from '@coze-common/chat-area-plugin-message-grab';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type SpaceProps } from '@coze-arch/bot-semi/Space';
|
||||
import {
|
||||
UIIconButton,
|
||||
UIToast,
|
||||
Space,
|
||||
Tooltip,
|
||||
UIModal,
|
||||
Form,
|
||||
UIFormTextArea,
|
||||
UIDropdown,
|
||||
UIDropdownMenu,
|
||||
UIDropdownItem,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import {
|
||||
IconCopy,
|
||||
IconMore,
|
||||
IconQuotation,
|
||||
IconWaringRed,
|
||||
} from '@coze-arch/bot-icons';
|
||||
import { type FileVO } from '@coze-arch/bot-api/filebox';
|
||||
import { fileboxApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { FileBoxListType, type UseBotStore } from '../types';
|
||||
import wrapperStyle from '../index.module.less';
|
||||
import { type Result } from '../hooks/use-file-list';
|
||||
import { COZE_CONNECTOR_ID } from '../const';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface ActionButtonsProps {
|
||||
botId: string;
|
||||
record: FileVO;
|
||||
type: FileBoxListType;
|
||||
reloadAsync: () => Promise<Result>;
|
||||
setIsFrozenCurrentHoverCardId?: (v: boolean) => void;
|
||||
spaceProps?: SpaceProps;
|
||||
useBotStore?: UseBotStore;
|
||||
isStore?: boolean;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
export const ActionButtons: FC<ActionButtonsProps> = props => {
|
||||
const {
|
||||
record,
|
||||
reloadAsync,
|
||||
setIsFrozenCurrentHoverCardId,
|
||||
spaceProps = {},
|
||||
botId,
|
||||
isStore = false,
|
||||
useBotStore,
|
||||
onCancel,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
FileName: name = '',
|
||||
Uri: uri = '',
|
||||
MainURL: url = '',
|
||||
Type: type,
|
||||
} = record;
|
||||
|
||||
const grabPluginIdForDebug = usePageRuntimeStore(state => state.grabPluginId);
|
||||
const grabPluginIdForStore = useBotStore?.(state => state.grabPluginId) || '';
|
||||
|
||||
const grabPluginId = isStore ? grabPluginIdForStore : grabPluginIdForDebug;
|
||||
|
||||
const isImage = type === FileBoxListType.Image;
|
||||
|
||||
const renameRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const handleCopy = async (value: string) => {
|
||||
await navigator.clipboard.writeText(value);
|
||||
UIToast.success(I18n.t(isImage ? 'filebox_0008' : 'filebox_0023'));
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
UIModal.error({
|
||||
title: I18n.t(isImage ? 'filebox_0013' : 'filebox_0022'),
|
||||
className: wrapperStyle['confirm-modal'],
|
||||
okButtonProps: {
|
||||
theme: 'solid',
|
||||
type: 'danger',
|
||||
},
|
||||
okText: I18n.t('Delete'),
|
||||
onOk: async () => {
|
||||
try {
|
||||
await fileboxApi.PublicBatchDeleteFiles({
|
||||
uris: [uri],
|
||||
detail_page_id: '',
|
||||
bot_id: botId,
|
||||
connector_id: COZE_CONNECTOR_ID,
|
||||
});
|
||||
UIToast.success(I18n.t(isImage ? 'filebox_0016' : 'filebox_0024'));
|
||||
reloadAsync();
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.FILEBOX, {
|
||||
error: error as Error,
|
||||
eventName: REPORT_EVENTS.FileBoxDeleteFile,
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: <IconWaringRed />,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRename = () => {
|
||||
const modal = UIModal.info({
|
||||
title: I18n.t('chatflow_agent_menu_rename'),
|
||||
className: wrapperStyle['confirm-modal'],
|
||||
content: (
|
||||
<Form<{ renamedValue: string }>
|
||||
initValues={{
|
||||
renamedValue: name,
|
||||
}}
|
||||
className={s['rename-form']}
|
||||
>
|
||||
<UIFormTextArea
|
||||
field="renamedValue"
|
||||
validate={(v: string) => {
|
||||
if (!v) {
|
||||
return I18n.t('file_name_cannot_be_empty');
|
||||
}
|
||||
}}
|
||||
noLabel
|
||||
ref={renameRef}
|
||||
maxCount={100}
|
||||
maxLength={100}
|
||||
rows={3}
|
||||
onChange={(v: string) => {
|
||||
modal.update({
|
||||
okButtonProps: {
|
||||
disabled: !v,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Form>
|
||||
),
|
||||
okButtonProps: {
|
||||
theme: 'solid',
|
||||
type: 'primary',
|
||||
},
|
||||
okText: I18n.t('Confirm'),
|
||||
onOk: async () => {
|
||||
try {
|
||||
await fileboxApi.PublicUpdateFile({
|
||||
update_items: {
|
||||
file_name: renameRef.current?.value,
|
||||
uri,
|
||||
},
|
||||
detail_page_id: '',
|
||||
bot_id: botId,
|
||||
connector_id: COZE_CONNECTOR_ID,
|
||||
});
|
||||
UIToast.success(I18n.t('Update_success'));
|
||||
reloadAsync();
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.FILEBOX, {
|
||||
error: error as Error,
|
||||
eventName: REPORT_EVENTS.FileBoxUpdateFile,
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: null,
|
||||
});
|
||||
};
|
||||
|
||||
const handleQuote = () => {
|
||||
publicEventCenter.emit(PublicEventNames.UpdateQuote, {
|
||||
grabPluginId,
|
||||
quote: [
|
||||
{
|
||||
type: isImage ? GrabElementType.IMAGE : GrabElementType.LINK,
|
||||
...(isImage
|
||||
? {
|
||||
src: url,
|
||||
}
|
||||
: { url }),
|
||||
children: [
|
||||
{
|
||||
text: name,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
onCancel?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<Space spacing={0} {...spaceProps}>
|
||||
<Tooltip content={I18n.t('ask_quote')}>
|
||||
<UIIconButton
|
||||
icon={<IconQuotation />}
|
||||
className={s['action-button']}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
handleQuote();
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip content={I18n.t('filebox_0007')}>
|
||||
<UIIconButton
|
||||
icon={<IconCopy />}
|
||||
className={s['action-button']}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
handleCopy(name);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<UIDropdown
|
||||
render={
|
||||
<UIDropdownMenu>
|
||||
<UIDropdownItem
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
handleRename();
|
||||
}}
|
||||
>
|
||||
{I18n.t('filebox_0010')}
|
||||
</UIDropdownItem>
|
||||
<UIDropdownItem
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
handleDelete();
|
||||
}}
|
||||
>
|
||||
{I18n.t('Delete')}
|
||||
</UIDropdownItem>
|
||||
</UIDropdownMenu>
|
||||
}
|
||||
onVisibleChange={v => {
|
||||
if (v) {
|
||||
setIsFrozenCurrentHoverCardId?.(true);
|
||||
} else {
|
||||
setIsFrozenCurrentHoverCardId?.(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<UIIconButton
|
||||
icon={<IconMore />}
|
||||
className={s['action-button']}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
</UIDropdown>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
// Coze的渠道id,在某些场景下需要写死传递给后端
|
||||
export const COZE_CONNECTOR_ID = '10000010';
|
||||
@@ -0,0 +1,47 @@
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
.table {
|
||||
:global {
|
||||
|
||||
// 去除顶部 margin 防止 header 出现意外的滚动
|
||||
.semi-table-wrapper {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
// 去除 table 自身的滚动,使用外层容器的滚动加载,配合 useInfiniteScroll 使用
|
||||
.semi-table-body {
|
||||
overflow: visible !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 文档名称列
|
||||
.column-document-name {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: rgba(29, 28, 35, 100%)
|
||||
}
|
||||
|
||||
// 文件大小列
|
||||
.column-document-size {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: rgba(29, 28, 35, 60%)
|
||||
}
|
||||
|
||||
// 上传时间列
|
||||
.column-document-update-time {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: rgba(29, 28, 35, 60%)
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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 dayjs from 'dayjs';
|
||||
import { getTypeIcon } from '@coze-data/knowledge-resource-processor-base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Typography, UITable } from '@coze-arch/bot-semi';
|
||||
import { type FileVO } from '@coze-arch/bot-api/filebox';
|
||||
import { type ColumnProps } from '@coze-arch/coze-design';
|
||||
|
||||
import { type FileBoxListProps, FileBoxListType } from '../types';
|
||||
import { type Result } from '../hooks/use-file-list';
|
||||
import { formatSize } from '../helpers/format-size';
|
||||
import { ActionButtons } from '../action-buttons';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface DocumentListProps extends FileBoxListProps {
|
||||
botId: string;
|
||||
documents: FileVO[];
|
||||
reloadAsync: () => Promise<Result>;
|
||||
}
|
||||
export const DocumentList: FC<DocumentListProps> = props => {
|
||||
const { documents, reloadAsync, botId, useBotStore, isStore, onCancel } =
|
||||
props;
|
||||
|
||||
const columns: ColumnProps<FileVO>[] = [
|
||||
{
|
||||
title: I18n.t('filebox_0018'),
|
||||
dataIndex: 'name',
|
||||
render: (_, record) => {
|
||||
const { Format: format, MainURL: url, FileName: name } = record;
|
||||
return (
|
||||
<div className={s['column-document-name']}>
|
||||
{getTypeIcon({
|
||||
type: format,
|
||||
url,
|
||||
inModal: true,
|
||||
})}
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
showTooltip: true,
|
||||
}}
|
||||
>
|
||||
{name || I18n.t('filebox_0047')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: I18n.t('datasets_unit_upload_field_size'),
|
||||
dataIndex: 'FileSize',
|
||||
render: text => (
|
||||
<div className={s['column-document-size']}>
|
||||
{formatSize(Number(text))}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: I18n.t('filebox_0020'),
|
||||
dataIndex: 'UpdateTime',
|
||||
render: text => (
|
||||
<div className={s['column-document-update-time']}>
|
||||
{dayjs.unix(Number(text)).format('YYYY-MM-DD HH:mm')}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: I18n.t('Actions'),
|
||||
dataIndex: 'action',
|
||||
width: 120,
|
||||
render: (_, record) => (
|
||||
<ActionButtons
|
||||
record={record}
|
||||
reloadAsync={reloadAsync}
|
||||
type={FileBoxListType.Document}
|
||||
spaceProps={{
|
||||
spacing: 8,
|
||||
}}
|
||||
botId={botId}
|
||||
useBotStore={useBotStore}
|
||||
isStore={isStore}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<UITable
|
||||
tableProps={{
|
||||
dataSource: documents,
|
||||
sticky: true,
|
||||
columns,
|
||||
rowKey: 'id',
|
||||
onRow: (record, index) => ({
|
||||
onClick: () => {
|
||||
window.open(record.MainURL);
|
||||
},
|
||||
}),
|
||||
className: s.table,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
.filter {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
|
||||
padding: 6px 0;
|
||||
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: rgba(29, 28, 35, 60%);
|
||||
}
|
||||
|
||||
.filter-item-active {
|
||||
color: rgba(77, 83, 232, 100%);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Divider } from '@coze-arch/bot-semi';
|
||||
|
||||
import { FileBoxListType } from '../types';
|
||||
import { useFileBoxListStore } from '../store';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export const FileBoxFilter: FC = () => {
|
||||
const fileListType = useFileBoxListStore(state => state.fileListType);
|
||||
const setFileListType = useFileBoxListStore(state => state.setFileListType);
|
||||
|
||||
return (
|
||||
<div className={s.filter}>
|
||||
<div
|
||||
className={classNames({
|
||||
[s['filter-item-active']]: fileListType === FileBoxListType.Image,
|
||||
})}
|
||||
onClick={() => setFileListType(FileBoxListType.Image)}
|
||||
>
|
||||
{I18n.t('filebox_0002')}
|
||||
</div>
|
||||
<Divider layout="vertical" />
|
||||
<div
|
||||
className={classNames({
|
||||
[s['filter-item-active']]: fileListType === FileBoxListType.Document,
|
||||
})}
|
||||
onClick={() => setFileListType(FileBoxListType.Document)}
|
||||
>
|
||||
{I18n.t('filebox_0003')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export enum Size {
|
||||
B = 'B',
|
||||
KB = 'KB',
|
||||
MB = 'MB',
|
||||
GB = 'GB',
|
||||
}
|
||||
const sizeB = 1024;
|
||||
const sizeKB = 1024 * sizeB;
|
||||
const sizeMB = 1024 * sizeKB;
|
||||
const sizeGB = 1024 * sizeMB;
|
||||
|
||||
export const formatFixed = (v: number) => v.toFixed(2);
|
||||
|
||||
export const formatSize = (v: number): string => {
|
||||
if (v > 0 && v < sizeB) {
|
||||
return `${formatFixed(v)}${Size.B}`;
|
||||
} else if (v < sizeKB) {
|
||||
return `${formatFixed(v / sizeB)}${Size.KB}`;
|
||||
} else if (v < sizeMB) {
|
||||
return `${formatFixed(v / sizeKB)}${Size.MB}`;
|
||||
} else if (v < sizeGB) {
|
||||
return `${formatFixed(v / sizeMB)}${Size.MB}`;
|
||||
}
|
||||
return `${formatFixed(v / sizeMB)}${Size.MB}`;
|
||||
};
|
||||
@@ -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 const prefixUri = (uri: string, url: string) => {
|
||||
const [filePrefix] = uri.split('/');
|
||||
const urlArray = url.split('/');
|
||||
const filePrefixIndex = urlArray.findIndex(text => text === filePrefix);
|
||||
const tosRegion = urlArray[filePrefixIndex - 1];
|
||||
const processedUri = `${tosRegion}/${uri}`;
|
||||
|
||||
return processedUri;
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 InfiniteScrollOptions } from 'ahooks/lib/useInfiniteScroll/types';
|
||||
import { useInfiniteScroll } from 'ahooks';
|
||||
import { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { type FileVO } from '@coze-arch/bot-api/filebox';
|
||||
import { fileboxApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { type FileBoxListType } from '../types';
|
||||
import { COZE_CONNECTOR_ID } from '../const';
|
||||
|
||||
export interface UseFileListParams {
|
||||
botId: string;
|
||||
searchValue?: string;
|
||||
type: FileBoxListType;
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
list: FileVO[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
export const useFileList = (
|
||||
params: UseFileListParams,
|
||||
options: InfiniteScrollOptions<Result>,
|
||||
) => {
|
||||
const { botId, searchValue, type } = params;
|
||||
|
||||
const fetchFileList = async (
|
||||
page: number,
|
||||
pageSize: number,
|
||||
): Promise<Result> => {
|
||||
let result: Result = {
|
||||
list: [],
|
||||
total: 0,
|
||||
};
|
||||
try {
|
||||
const res = await fileboxApi.FileList({
|
||||
// 前端从 1 开始计数,方便 Math.ceil 计算,传给后端时手动减 1
|
||||
page_num: page - 1,
|
||||
page_size: pageSize,
|
||||
bid: botId,
|
||||
file_name: searchValue,
|
||||
file_type: type,
|
||||
connector_id: COZE_CONNECTOR_ID,
|
||||
});
|
||||
result = {
|
||||
list: res.list || [],
|
||||
total: res.total_count || 0,
|
||||
};
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.FILEBOX, {
|
||||
eventName: REPORT_EVENTS.FileBoxListFile,
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
return useInfiniteScroll<Result>(
|
||||
async d => {
|
||||
const p = d ? Math.ceil(d.list.length / PAGE_SIZE) + 1 : 1;
|
||||
return fetchFileList(p, PAGE_SIZE);
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
...options,
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import { type UnitItem } from '@coze-data/knowledge-resource-processor-core';
|
||||
import {
|
||||
transformUnitList,
|
||||
getFileExtension,
|
||||
getBase64,
|
||||
} from '@coze-data/knowledge-resource-processor-base';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { FileBizType } from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi } from '@coze-arch/bot-api';
|
||||
|
||||
export const useRetry = (params: {
|
||||
unitList: UnitItem[];
|
||||
setUnitList: (unitList: UnitItem[]) => void;
|
||||
}) => {
|
||||
const { unitList, setUnitList } = params;
|
||||
|
||||
const onRetry = async (record: UnitItem, index: number) => {
|
||||
try {
|
||||
const { fileInstance } = record;
|
||||
if (fileInstance) {
|
||||
const { name } = fileInstance;
|
||||
const extension = getFileExtension(name);
|
||||
const base64 = await getBase64(fileInstance);
|
||||
const result = await DeveloperApi.UploadFile({
|
||||
file_head: {
|
||||
file_type: extension,
|
||||
biz_type: FileBizType.BIZ_BOT_DATASET,
|
||||
},
|
||||
data: base64,
|
||||
});
|
||||
|
||||
setUnitList(
|
||||
transformUnitList({
|
||||
unitList,
|
||||
data: result?.data,
|
||||
fileInstance,
|
||||
index,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
|
||||
eventName: REPORT_EVENTS.KnowledgeUploadFile,
|
||||
error,
|
||||
});
|
||||
}
|
||||
};
|
||||
return onRetry;
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { dataReporter, DataNamespace } from '@coze-data/reporter';
|
||||
import {
|
||||
type UnitItem,
|
||||
UnitType,
|
||||
UploadStatus,
|
||||
} from '@coze-data/knowledge-resource-processor-core';
|
||||
import {
|
||||
UploadUnitFile,
|
||||
UploadUnitTable,
|
||||
} from '@coze-data/knowledge-resource-processor-base';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UIModal, UIToast } from '@coze-arch/bot-semi';
|
||||
import { fileboxApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { FileBoxListType } from '../types';
|
||||
import s from '../index.module.less';
|
||||
import { prefixUri } from '../helpers/prefix-uri';
|
||||
import { COZE_CONNECTOR_ID } from '../const';
|
||||
import { useRetry } from './use-retry';
|
||||
import { type Result } from './use-file-list';
|
||||
|
||||
export interface UseUploadModalParams {
|
||||
botId: string;
|
||||
fileListType: FileBoxListType;
|
||||
reloadAsync: () => Promise<Result>;
|
||||
}
|
||||
|
||||
export const useUploadModal = (params: UseUploadModalParams) => {
|
||||
const { botId, fileListType, reloadAsync } = params;
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [unitList, setUnitList] = useState<UnitItem[]>([]);
|
||||
|
||||
const hideUploadFile = false;
|
||||
const AddUnitMaxLimit = 10;
|
||||
|
||||
const onRetry = useRetry({ unitList, setUnitList });
|
||||
|
||||
const submitButtonDisabled =
|
||||
unitList.length === 0 ||
|
||||
unitList.some(
|
||||
i =>
|
||||
/**
|
||||
* 1. 未上传成功的
|
||||
* 2. 校验失败的
|
||||
* 3. 名字为空的(名字为空暂不影响 validateMessage,所以需要单独判断)
|
||||
*/
|
||||
i.status !== UploadStatus.SUCCESS || i.validateMessage || !i.name,
|
||||
);
|
||||
|
||||
const handleUnitListUpdate = (data: UnitItem[]) => {
|
||||
// 防止重命名后再上传被覆盖
|
||||
const newData = data.map(i => {
|
||||
let resultName = i.name;
|
||||
unitList.forEach(u => {
|
||||
if (
|
||||
u.uid === i.uid &&
|
||||
u.status === i.status &&
|
||||
u.status === UploadStatus.SUCCESS
|
||||
) {
|
||||
resultName = u.name;
|
||||
}
|
||||
});
|
||||
return {
|
||||
...i,
|
||||
name: resultName,
|
||||
};
|
||||
});
|
||||
|
||||
setUnitList(newData);
|
||||
};
|
||||
|
||||
const handleUploadSubmit = async () => {
|
||||
try {
|
||||
const {
|
||||
DestFiles = [],
|
||||
SuccessNum,
|
||||
FailNum,
|
||||
} = await fileboxApi.UploadFiles({
|
||||
source_files: unitList.map(i => {
|
||||
const { uri, url, name } = i;
|
||||
return {
|
||||
file_uri: prefixUri(uri, url),
|
||||
file_name: name,
|
||||
};
|
||||
}),
|
||||
bid: botId,
|
||||
cid: COZE_CONNECTOR_ID,
|
||||
biz_type:
|
||||
fileListType === FileBoxListType.Image ? 'coze-img' : 'coze-file',
|
||||
});
|
||||
const failedDestFiles = DestFiles.filter(i => i.status !== 0).map(i => ({
|
||||
...i,
|
||||
errorMessage:
|
||||
i.status === 708252039
|
||||
? I18n.t('file_name_exist')
|
||||
: I18n.t('Upload_failed'),
|
||||
}));
|
||||
UIToast.success(
|
||||
I18n.t('upload_success_failed_count', {
|
||||
successNum: SuccessNum,
|
||||
failedNum: FailNum,
|
||||
}),
|
||||
);
|
||||
if (failedDestFiles.length === 0) {
|
||||
await reloadAsync();
|
||||
setVisible(false);
|
||||
} else {
|
||||
const newUnitList = failedDestFiles.map(i => {
|
||||
const unit = unitList.find(
|
||||
u => prefixUri(u.uri, u.url) === i.file_uri,
|
||||
);
|
||||
return {
|
||||
...unit,
|
||||
dynamicErrorMessage: i.errorMessage,
|
||||
};
|
||||
});
|
||||
setUnitList(newUnitList as UnitItem[]);
|
||||
}
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.FILEBOX, {
|
||||
error: error as Error,
|
||||
eventName: REPORT_EVENTS.FileBoxUploadFile,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// reset
|
||||
useEffect(() => {
|
||||
if (!visible) {
|
||||
setUnitList([]);
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
return {
|
||||
open: () => setVisible(true),
|
||||
close: () => setVisible(false),
|
||||
node: (
|
||||
<UIModal
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(false)}
|
||||
title={I18n.t('datasets_createFileModel_step2')}
|
||||
width={792}
|
||||
onOk={handleUploadSubmit}
|
||||
keepDOM={false}
|
||||
okButtonProps={{
|
||||
disabled: submitButtonDisabled,
|
||||
}}
|
||||
className={s['upload-modal']}
|
||||
>
|
||||
<UploadUnitFile
|
||||
action=""
|
||||
maxSizeMB={20}
|
||||
accept={
|
||||
fileListType === FileBoxListType.Image
|
||||
? '.png,.jpg,.jpeg'
|
||||
: '.pdf,.txt,.doc,.docx'
|
||||
}
|
||||
dragMainText={I18n.t(
|
||||
fileListType === FileBoxListType.Image
|
||||
? 'knowledge_photo_004'
|
||||
: 'datasets_createFileModel_step2_UploadDoc',
|
||||
)}
|
||||
dragSubText={
|
||||
fileListType === FileBoxListType.Image
|
||||
? I18n.t('knowledge_photo_005')
|
||||
: I18n.t('datasets_createFileModel_step2_UploadDoc_description', {
|
||||
fileFormat: 'PDF、TXT、DOC、DOCX',
|
||||
maxDocNum: 300,
|
||||
filesize: '20MB',
|
||||
pdfPageNum: 250,
|
||||
})
|
||||
}
|
||||
limit={AddUnitMaxLimit}
|
||||
unitList={unitList}
|
||||
multiple={AddUnitMaxLimit > 1}
|
||||
style={
|
||||
hideUploadFile ? { visibility: 'hidden', height: 0 } : undefined
|
||||
}
|
||||
setUnitList={handleUnitListUpdate}
|
||||
onFinish={handleUnitListUpdate}
|
||||
/>
|
||||
{unitList.length > 0 ? (
|
||||
<div className="overflow-y-auto my-[25px]">
|
||||
<UploadUnitTable
|
||||
type={UnitType.IMAGE_FILE}
|
||||
edit={true}
|
||||
unitList={unitList}
|
||||
onChange={setUnitList}
|
||||
onRetry={onRetry}
|
||||
inModal
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</UIModal>
|
||||
),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
.card-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
// Card 整体
|
||||
.card {
|
||||
cursor: auto;
|
||||
width: 209px;
|
||||
height: 214px;
|
||||
border-radius: 8px;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 14px 0 rgba(0, 0, 0, 8%);
|
||||
}
|
||||
|
||||
:global {
|
||||
|
||||
// 固定高度142,超出高度的图片,截取居中部分展示
|
||||
.semi-card-cover {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 142px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Card 封面
|
||||
.card-cover {
|
||||
cursor: pointer;
|
||||
border-radius: 8px 8px 0 0;
|
||||
|
||||
// 设置最小高度142,保证填满封面
|
||||
img {
|
||||
min-height: 142px;
|
||||
}
|
||||
}
|
||||
|
||||
// Card 内容区 (title + description)
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.photo-name {
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: rgba(29, 28, 35, 100%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Card 底部栏(时间 + 操作按钮)
|
||||
.card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 24px;
|
||||
|
||||
.create-time {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgba(28, 29, 35, 35%)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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 dayjs from 'dayjs';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Card, CardGroup, Typography, Image } from '@coze-arch/bot-semi';
|
||||
import { type FileVO } from '@coze-arch/bot-api/filebox';
|
||||
|
||||
import { type FileBoxListProps, FileBoxListType } from '../types';
|
||||
import { type Result } from '../hooks/use-file-list';
|
||||
import { ActionButtons } from '../action-buttons';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface ImageListProps extends FileBoxListProps {
|
||||
images: FileVO[];
|
||||
reloadAsync: () => Promise<Result>;
|
||||
}
|
||||
|
||||
export const ImageList: FC<ImageListProps> = props => {
|
||||
const { images, reloadAsync, botId, useBotStore, isStore, onCancel } = props;
|
||||
const [currentHoverCardId, setCurrentHoverCardId] = useState<string>('');
|
||||
const [isFrozenCurrentHoverCardId, setIsFrozenCurrentHoverCardId] =
|
||||
useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<CardGroup spacing={12} className={s['card-group']}>
|
||||
{images?.map(i => {
|
||||
const {
|
||||
// MainURL 加载太慢了,列表中使用 ThumbnailURL 进行缩略图展示
|
||||
ThumbnailURL: url,
|
||||
MainURL: previewUrl,
|
||||
FileID: id,
|
||||
FileName: name,
|
||||
UpdateTime: updateTime,
|
||||
} = i || {};
|
||||
const isHover = currentHoverCardId === id;
|
||||
|
||||
const onMouseEnter = () => {
|
||||
setCurrentHoverCardId(id || '');
|
||||
};
|
||||
|
||||
const onMouseLeave = () => {
|
||||
if (isFrozenCurrentHoverCardId) {
|
||||
return;
|
||||
}
|
||||
setCurrentHoverCardId('');
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={id}
|
||||
cover={
|
||||
<Image
|
||||
src={url}
|
||||
// 仅设置宽度,高度会按图片原比例自动缩放
|
||||
width={209}
|
||||
className={s['card-cover']}
|
||||
preview={{
|
||||
src: previewUrl,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
headerLine={false}
|
||||
bodyStyle={{
|
||||
padding: '12px',
|
||||
}}
|
||||
className={s.card}
|
||||
>
|
||||
<div
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
className={s['card-content']}
|
||||
>
|
||||
<Typography.Text
|
||||
className={s['photo-name']}
|
||||
ellipsis={{
|
||||
showTooltip: true,
|
||||
}}
|
||||
>
|
||||
{name || I18n.t('filebox_0047')}
|
||||
</Typography.Text>
|
||||
<div className={s['card-footer']}>
|
||||
<Typography.Text className={s['create-time']}>
|
||||
{dayjs.unix(Number(updateTime)).format('YYYY-MM-DD HH:mm')}
|
||||
</Typography.Text>
|
||||
{isHover ? (
|
||||
<ActionButtons
|
||||
record={i}
|
||||
reloadAsync={reloadAsync}
|
||||
type={FileBoxListType.Document}
|
||||
setIsFrozenCurrentHoverCardId={
|
||||
setIsFrozenCurrentHoverCardId
|
||||
}
|
||||
botId={botId}
|
||||
useBotStore={useBotStore}
|
||||
isStore={isStore}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</CardGroup>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.filebox-list {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
|
||||
// 移除全局样式
|
||||
:global {
|
||||
.semi-spin-block.semi-spin {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.semi-spin-children {
|
||||
height: auto
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-list {
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: 100%;
|
||||
height: calc(100% - 52px);
|
||||
|
||||
.file-list-spin {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
|
||||
.spin {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// UI稿的 confirm modal 和 semi 默认的不一样,需要手动调整样式
|
||||
.confirm-modal {
|
||||
:global {
|
||||
.semi-modal-header {
|
||||
margin-bottom: 16px;
|
||||
|
||||
// icon 和 title 的间距
|
||||
.semi-modal-icon-wrapper {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
// title 颜色
|
||||
.semi-modal-confirm-title-text {
|
||||
color: rgba(29, 28, 35, 100%);
|
||||
}
|
||||
|
||||
// 关闭 icon 的 hover 颜色
|
||||
.semi-button:hover {
|
||||
background-color: rgba(46, 46, 56, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
.semi-modal-body {
|
||||
margin: 0;
|
||||
padding: 16px 0;
|
||||
|
||||
.semi-modal-confirm-content {
|
||||
color: rgba(29, 28, 35, 100%)
|
||||
}
|
||||
}
|
||||
|
||||
.semi-modal-footer button {
|
||||
min-width: 96px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// UIModal 的背景颜色不符,需要调整
|
||||
.upload-modal{
|
||||
:global{
|
||||
.semi-modal-content{
|
||||
background: #f7f7fa !important
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 { useEffect, useRef, type FC } from 'react';
|
||||
|
||||
import { debounce } from 'lodash-es';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Space, UIButton, UISearch, Spin, UIEmpty } from '@coze-arch/bot-semi';
|
||||
import { IconSegmentEmpty } from '@coze-arch/bot-icons';
|
||||
|
||||
import { type FileBoxListProps, FileBoxListType } from './types';
|
||||
import { useFileBoxListStore } from './store';
|
||||
import { ImageList } from './image-list';
|
||||
import { useUploadModal } from './hooks/use-upload-modal';
|
||||
import { useFileList } from './hooks/use-file-list';
|
||||
import { FileBoxFilter } from './filebox-filter';
|
||||
import { DocumentList } from './document-list';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export const FileBoxList: FC<FileBoxListProps> = props => {
|
||||
const { botId } = props;
|
||||
|
||||
const searchValue = useFileBoxListStore(state => state.searchValue);
|
||||
const setSearchValue = useFileBoxListStore(state => state.setSearchValue);
|
||||
const fileListType = useFileBoxListStore(state => state.fileListType);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { data, loading, loadingMore, reloadAsync, noMore } = useFileList(
|
||||
{
|
||||
botId,
|
||||
searchValue,
|
||||
type: fileListType,
|
||||
},
|
||||
{
|
||||
isNoMore: d => !!(d && d.list.length >= d.total),
|
||||
target: ref,
|
||||
},
|
||||
);
|
||||
|
||||
// 手动控制 data 加载时机
|
||||
useEffect(() => {
|
||||
if (botId) {
|
||||
reloadAsync();
|
||||
|
||||
// 重新加载时,回到最顶部
|
||||
ref.current?.scrollTo?.({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}, [searchValue, botId, fileListType]);
|
||||
|
||||
const items = data?.list || [];
|
||||
|
||||
const { open, node } = useUploadModal({ botId, fileListType, reloadAsync });
|
||||
|
||||
const debounceSearch = debounce((v: string) => {
|
||||
setSearchValue(v);
|
||||
}, 300);
|
||||
|
||||
const isImage = fileListType === FileBoxListType.Image;
|
||||
|
||||
const getEmptyTitle = () => {
|
||||
if (searchValue) {
|
||||
return I18n.t(isImage ? 'filebox_010' : 'filebox_011');
|
||||
}
|
||||
return I18n.t(isImage ? 'filebox_0017' : 'filebox_0025');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={s['filebox-list']}>
|
||||
<div className={s.header}>
|
||||
{/* 切换图片/文档 */}
|
||||
<FileBoxFilter />
|
||||
|
||||
<Space spacing={12}>
|
||||
{/* 搜索框 */}
|
||||
<UISearch
|
||||
placeholder={I18n.t(
|
||||
'card_builder_dataEditor_get_errormsg_please_enter',
|
||||
)}
|
||||
onChange={debounceSearch}
|
||||
/>
|
||||
|
||||
{/* 上传按钮 */}
|
||||
<UIButton type="primary" theme="solid" onClick={open}>
|
||||
{I18n.t('datasets_createFileModel_step2')}
|
||||
</UIButton>
|
||||
</Space>
|
||||
</div>
|
||||
<div className={s['file-list']} ref={ref}>
|
||||
<Spin
|
||||
spinning={loading}
|
||||
wrapperClassName={s['file-list-spin']}
|
||||
childStyle={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
// 防止切换 fileListType 时 items 数量不一致,导致 loading 闪烁
|
||||
display: loading ? 'none' : 'block',
|
||||
}}
|
||||
>
|
||||
{items.length <= 0 ? (
|
||||
<UIEmpty
|
||||
empty={{
|
||||
icon: <IconSegmentEmpty />,
|
||||
title: getEmptyTitle(),
|
||||
}}
|
||||
/>
|
||||
) : isImage ? (
|
||||
<ImageList images={items} reloadAsync={reloadAsync} {...props} />
|
||||
) : (
|
||||
<DocumentList
|
||||
documents={items}
|
||||
reloadAsync={reloadAsync}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
</Spin>
|
||||
<div className={s.footer}>
|
||||
{!noMore && (
|
||||
<Spin
|
||||
spinning={loadingMore}
|
||||
tip={I18n.t('loading')}
|
||||
wrapperClassName={s.spin}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{node}
|
||||
</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 { devtools } from 'zustand/middleware';
|
||||
import { create } from 'zustand';
|
||||
|
||||
import { FileBoxListType } from './types';
|
||||
|
||||
interface FileBoxListState {
|
||||
fileListType: FileBoxListType;
|
||||
searchValue: string;
|
||||
}
|
||||
|
||||
interface FileBoxListAction {
|
||||
setFileListType: (v: FileBoxListType) => void;
|
||||
setSearchValue: (v: string) => void;
|
||||
}
|
||||
|
||||
export const useFileBoxListStore = create<
|
||||
FileBoxListState & FileBoxListAction
|
||||
>()(
|
||||
devtools((set, get) => ({
|
||||
fileListType: FileBoxListType.Image,
|
||||
searchValue: '',
|
||||
setFileListType: (v: FileBoxListType) => {
|
||||
set({ fileListType: v });
|
||||
},
|
||||
setSearchValue: (v: string) => {
|
||||
set({ searchValue: v });
|
||||
},
|
||||
})),
|
||||
);
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type StoreApi, type UseBoundStore } from 'zustand';
|
||||
|
||||
export enum FileBoxListType {
|
||||
Image = 1,
|
||||
Document = 2,
|
||||
}
|
||||
|
||||
export type UseBotStore = UseBoundStore<
|
||||
StoreApi<{
|
||||
grabPluginId: string;
|
||||
}>
|
||||
>;
|
||||
|
||||
export interface FileBoxListProps {
|
||||
botId: string;
|
||||
useBotStore?: UseBotStore;
|
||||
isStore?: boolean;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.memory-debug-dropdown {
|
||||
min-width: 120px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.memory-debug-dropdown-item {
|
||||
height: 32px !important;
|
||||
padding: 8px !important;
|
||||
|
||||
line-height: 20px;
|
||||
color: var(--coz-fg-primary);
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
&:not(.semi-dropdown-item-active):hover{
|
||||
background-color:var(--coz-mg-secondary-hovered) !important
|
||||
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-dropdown-item-icon {
|
||||
font-size: 16px;
|
||||
color: var(--coz-fg-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 FC } from 'react';
|
||||
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
import { UIDropdownItem, UIDropdownMenu } from '@coze-arch/bot-semi';
|
||||
|
||||
import {
|
||||
type MemoryModule,
|
||||
type MemoryDebugDropdownMenuItem,
|
||||
} from '../../types';
|
||||
import { useSendTeaEventForMemoryDebug } from '../../hooks/use-send-tea-event-for-memory-debug';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface MemoryDebugDropdownProps {
|
||||
menuList: MemoryDebugDropdownMenuItem[];
|
||||
onClickItem: (memoryModule: MemoryModule) => void;
|
||||
isStore?: boolean;
|
||||
}
|
||||
|
||||
export const MemoryDebugDropdown: FC<MemoryDebugDropdownProps> = props => {
|
||||
const { menuList, isStore = false, onClickItem } = props;
|
||||
|
||||
const sendTeaEventForMemoryDebug = useSendTeaEventForMemoryDebug({ isStore });
|
||||
|
||||
const handleClickMenu = (memoryModule: MemoryModule) => {
|
||||
sendTeaEventForMemoryDebug(memoryModule);
|
||||
onClickItem(memoryModule);
|
||||
};
|
||||
|
||||
return (
|
||||
<UIDropdownMenu className={styles['memory-debug-dropdown']}>
|
||||
{menuList?.map(item => (
|
||||
<UIDropdownItem
|
||||
data-dtestid={`${BotE2e.BotMemoryDebugDropdownItem}.${item.name}`}
|
||||
icon={item.icon}
|
||||
onClick={() => handleClickMenu(item.name)}
|
||||
className={styles['memory-debug-dropdown-item']}
|
||||
>
|
||||
{item.label}
|
||||
</UIDropdownItem>
|
||||
))}
|
||||
</UIDropdownMenu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.tabs_memory {
|
||||
height: 100%;
|
||||
|
||||
:global {
|
||||
.semi-tabs-tab-line.semi-tabs-tab-left.semi-tabs-tab-active {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1D1C23;
|
||||
|
||||
background: rgba(28, 28, 35, 5%);
|
||||
border-left: none;
|
||||
border-radius: 8px;
|
||||
|
||||
}
|
||||
|
||||
.semi-tabs-bar-left {
|
||||
box-sizing: border-box;
|
||||
width: 216px;
|
||||
padding: 24px 12px 0;
|
||||
|
||||
background: #F0F0F5;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.semi-tabs-bar-line.semi-tabs-bar-left .semi-tabs-tab {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.semi-tabs-tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
height: 40px;
|
||||
margin-bottom: 4px;
|
||||
padding: 0 12px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #1D1C23;
|
||||
|
||||
&:hover {
|
||||
border-left: none;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.semi-tabs-content {
|
||||
flex: 1;
|
||||
padding: 12px 12px 0;
|
||||
|
||||
.semi-tabs-pane-active.semi-tabs-pane {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.semi-tabs-pane,
|
||||
.semi-tabs-pane-motion-overlay {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.memory-debug-modal {
|
||||
:global {
|
||||
.semi-modal-content {
|
||||
padding: 0;
|
||||
background-color: #F7F7FA !important;
|
||||
|
||||
.semi-modal-header {
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
border-bottom: 1px solid rgba(29, 28, 35, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.memory-debug-modal-tabs-tab{
|
||||
svg{
|
||||
margin-right: 8px;
|
||||
font-size: 16px;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { type Attributes } from 'react';
|
||||
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { TabPane, Tabs, useUIModal } from '@coze-arch/bot-semi';
|
||||
|
||||
import {
|
||||
type MemoryModule,
|
||||
type MemoryDebugDropdownMenuItem,
|
||||
} from '../../types';
|
||||
import { useSendTeaEventForMemoryDebug } from '../../hooks/use-send-tea-event-for-memory-debug';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface MemoryDebugModalProps {
|
||||
memoryModule: MemoryModule | undefined;
|
||||
menuList: MemoryDebugDropdownMenuItem[];
|
||||
isStore: boolean;
|
||||
setMemoryModule: (type: MemoryModule) => void;
|
||||
}
|
||||
|
||||
export const useMemoryDebugModal = ({
|
||||
memoryModule,
|
||||
menuList,
|
||||
setMemoryModule,
|
||||
isStore,
|
||||
}: MemoryDebugModalProps) => {
|
||||
const sendTeaEventForMemoryDebug = useSendTeaEventForMemoryDebug({ isStore });
|
||||
|
||||
const defaultModule = menuList[0]?.name;
|
||||
|
||||
const curMemoryModule = memoryModule || defaultModule;
|
||||
|
||||
const { modal, open, close } = useUIModal({
|
||||
type: 'info',
|
||||
width: 1138,
|
||||
height: 665,
|
||||
className: styles['memory-debug-modal'],
|
||||
bodyStyle: {
|
||||
padding: 0,
|
||||
},
|
||||
title: I18n.t('database_memory_menu'),
|
||||
centered: true,
|
||||
footer: null,
|
||||
onCancel: () => {
|
||||
sendTeaEventForMemoryDebug(curMemoryModule, { action: 'turn_off' });
|
||||
setMemoryModule(defaultModule);
|
||||
close();
|
||||
},
|
||||
});
|
||||
|
||||
const onChange = (key: MemoryModule) => {
|
||||
setMemoryModule(key);
|
||||
sendTeaEventForMemoryDebug(key);
|
||||
};
|
||||
|
||||
return {
|
||||
node: modal(
|
||||
<Tabs
|
||||
className={styles.tabs_memory}
|
||||
tabPosition="left"
|
||||
activeKey={curMemoryModule}
|
||||
onChange={onChange as (k: string) => void}
|
||||
lazyRender
|
||||
>
|
||||
{menuList.map(item => (
|
||||
<TabPane
|
||||
itemKey={item.name}
|
||||
key={item.name}
|
||||
tab={
|
||||
<span
|
||||
data-dtestid={`${BotE2e.BotMemoryDebugModalTab}.${item.name}`}
|
||||
className={styles['memory-debug-modal-tabs-tab']}
|
||||
>
|
||||
{item.icon}
|
||||
{item.label}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{/* 给 children 传递 onCancel 参数,用于从内部关闭弹窗 */}
|
||||
{React.isValidElement(item.component)
|
||||
? React.cloneElement(item.component, {
|
||||
onCancel: close,
|
||||
} as unknown as Attributes)
|
||||
: item.component}
|
||||
</TabPane>
|
||||
))}
|
||||
</Tabs>,
|
||||
),
|
||||
open,
|
||||
close,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
// @import '../../../../../../../assets/styles/common.less';
|
||||
|
||||
.variable-debug-container {
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
height: 100%;
|
||||
|
||||
.keyword {
|
||||
flex-shrink: 0;
|
||||
width: 140px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.update_time {
|
||||
flex-shrink: 0;
|
||||
width: 110px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.modal-container-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
|
||||
.keyword,
|
||||
.value,
|
||||
.update_time {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--Light-usage-text---color-text-0, #1D1C23);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-container-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.system_row {
|
||||
font-size: 12px;
|
||||
color: var(--Light-usage-text---color-text-1, rgba(29, 28, 35, 80%));
|
||||
|
||||
|
||||
:global {
|
||||
.semi-typography {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
color: var(--Light-usage-text---color-text-1, rgba(29, 28, 35, 80%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.keyword {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.update_time {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-2, rgb(29 28 35 / 60%));
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.operate-area {
|
||||
flex-shrink: 0;
|
||||
padding: 24px 0;
|
||||
border-top: 1px solid var(--light-usage-border-color-border, rgb(29 28 35 / 8%));
|
||||
}
|
||||
|
||||
|
||||
.hover-tip {
|
||||
max-width: 410px !important;
|
||||
}
|
||||
|
||||
.variable-debug-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.debug-header-deal-button {
|
||||
height: 26px !important;
|
||||
margin-left: 8px !important;
|
||||
padding: 4px !important;
|
||||
border-radius: 4px !important;
|
||||
|
||||
&:hover {
|
||||
background: var(--light-usage-fill-color-fill-0,
|
||||
rgb(46 46 56 / 4%)) !important;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--light-usage-fill-color-fill-1,
|
||||
rgb(46 46 56 / 8%)) !important;
|
||||
}
|
||||
|
||||
&.click {
|
||||
background: var(--light-usage-fill-color-fill-2,
|
||||
rgb(46 46 56 / 12%)) !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* 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 classNames from 'classnames';
|
||||
import { useReactive } from 'ahooks';
|
||||
import { IconAlertCircle } from '@douyinfe/semi-icons';
|
||||
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import { dataReporter, DataNamespace } from '@coze-data/reporter';
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import {
|
||||
Modal,
|
||||
Spin,
|
||||
Toast,
|
||||
Tooltip,
|
||||
Typography,
|
||||
UIButton,
|
||||
UIInput,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import type { KVItem } from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { formatDate } from '../../utils';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
/* eslint-disable */
|
||||
|
||||
const ProfileInput = ({
|
||||
className,
|
||||
value,
|
||||
botId,
|
||||
keyword,
|
||||
onClear,
|
||||
afterUpdate,
|
||||
}: {
|
||||
className?: string;
|
||||
value?: string;
|
||||
botId: string;
|
||||
keyword: string;
|
||||
onClear: () => void;
|
||||
afterUpdate?: () => void;
|
||||
}) => {
|
||||
const [inputV, setInputV] = useState(value);
|
||||
useEffect(() => setInputV(value), [value]);
|
||||
const onUpdate = async () => {
|
||||
try {
|
||||
if (inputV === value) {
|
||||
return;
|
||||
}
|
||||
const resp = (await MemoryApi.SetKvMemory({
|
||||
bot_id: botId,
|
||||
data: [{ keyword, value: inputV }],
|
||||
})) as { code: number };
|
||||
if (resp.code === 0) {
|
||||
Toast.success({
|
||||
content: I18n.t('Update_success'),
|
||||
showClose: false,
|
||||
});
|
||||
afterUpdate?.();
|
||||
} else {
|
||||
Toast.warning({
|
||||
content: I18n.t('Update_failed'),
|
||||
showClose: false,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.VARIABLE, {
|
||||
eventName: REPORT_EVENTS.VariableSetValue,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error
|
||||
: new CustomError(
|
||||
REPORT_EVENTS.VariableSetValue,
|
||||
`${REPORT_EVENTS.VariableSetValue}: operation fail`,
|
||||
),
|
||||
meta: {
|
||||
bot_id: botId,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
data-dtestid={`${BotE2e.BotVariableDebugModalValueInput}.${keyword}`}
|
||||
>
|
||||
<UIInput
|
||||
showClear
|
||||
value={inputV}
|
||||
onChange={v => setInputV(v)}
|
||||
onClear={onClear}
|
||||
onBlur={onUpdate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const VariableDebug = () => {
|
||||
const botId = useBotInfoStore(store => store.botId);
|
||||
const variables = useBotSkillStore(store => store.variables);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showResetModal, setShowResetModal] = useState(false);
|
||||
const $list = useReactive({
|
||||
current: [] as (KVItem & { loading?: boolean })[],
|
||||
});
|
||||
const getKvList = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const resp = await MemoryApi.GetPlayGroundMemory({
|
||||
bot_id: botId,
|
||||
});
|
||||
if (resp?.memories) {
|
||||
const data = variables.map(i => {
|
||||
const item = resp.memories?.find(j => j.keyword === i.key) || {};
|
||||
return {
|
||||
...item,
|
||||
keyword: i.key,
|
||||
loading: false,
|
||||
};
|
||||
});
|
||||
$list.current = data as KVItem[];
|
||||
}
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.VARIABLE, {
|
||||
eventName: REPORT_EVENTS.VariableGetValue,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error
|
||||
: new CustomError(
|
||||
REPORT_EVENTS.VariableSetValue,
|
||||
`${REPORT_EVENTS.VariableSetValue}: get list fail`,
|
||||
),
|
||||
meta: {
|
||||
bot_id: botId,
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
getKvList();
|
||||
}, []);
|
||||
|
||||
const onDelete = async (keyword?: string) => {
|
||||
try {
|
||||
const resp = (await MemoryApi.DelProfileMemory({
|
||||
bot_id: botId,
|
||||
keywords: keyword ? [keyword] : undefined,
|
||||
})) as unknown as { code: number };
|
||||
if (resp.code === 0) {
|
||||
Toast.success({
|
||||
content: I18n.t('variable_reset_succ_tips'),
|
||||
showClose: false,
|
||||
});
|
||||
getKvList();
|
||||
} else {
|
||||
Toast.warning({
|
||||
content: I18n.t('variable_reset_fail_tips'),
|
||||
showClose: false,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.VARIABLE, {
|
||||
eventName: REPORT_EVENTS.VariableDeleteValue,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error
|
||||
: new CustomError(
|
||||
REPORT_EVENTS.VariableSetValue,
|
||||
`${REPORT_EVENTS.VariableSetValue}: operation fail`,
|
||||
),
|
||||
meta: {
|
||||
bot_id: botId,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className={s['variable-debug-container']}>
|
||||
<Spin spinning={loading}>
|
||||
<div className={s['modal-container-title']}>
|
||||
<div
|
||||
className={s.keyword}
|
||||
data-testid={BotE2e.BotVariableDebugModalNameTitleText}
|
||||
>
|
||||
{I18n.t('variable_field_name')}
|
||||
</div>
|
||||
<div
|
||||
className={s.value}
|
||||
data-testid={BotE2e.BotVariableDebugModalValueTitleText}
|
||||
>
|
||||
{I18n.t('variable_field_value')}
|
||||
</div>
|
||||
<div
|
||||
className={s.update_time}
|
||||
data-testid={BotE2e.BotVariableDebugModalEditDateTitleText}
|
||||
>
|
||||
{I18n.t('variable_edit_time')}
|
||||
</div>
|
||||
</div>
|
||||
{$list.current.map(i => {
|
||||
if (!i.keyword) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
key={i.keyword}
|
||||
className={classNames(s['modal-container-row'], {
|
||||
[s.system_row]: i.is_system,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={s.keyword}
|
||||
data-dtestid={`${BotE2e.BotVariableDebugModalNameText}.${i.keyword}`}
|
||||
>
|
||||
<Paragraph
|
||||
ellipsis={{
|
||||
rows: 1,
|
||||
showTooltip: {
|
||||
opts: {
|
||||
style: {
|
||||
maxWidth: 234,
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{i.keyword}
|
||||
</Paragraph>
|
||||
</div>
|
||||
{/* 是否为系统字段 */}
|
||||
{i.is_system ? (
|
||||
<Paragraph
|
||||
data-dtestid={`${BotE2e.BotVariableDebugModalValueInput}.${i.keyword}`}
|
||||
ellipsis={{
|
||||
rows: 1,
|
||||
showTooltip: {
|
||||
opts: {
|
||||
style: {
|
||||
maxWidth: 234,
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{i.value}
|
||||
</Paragraph>
|
||||
) : (
|
||||
<ProfileInput
|
||||
className={s.value}
|
||||
value={
|
||||
i.value ||
|
||||
variables?.find(item => item.key === i.keyword)
|
||||
?.default_value
|
||||
}
|
||||
keyword={i.keyword || ''}
|
||||
botId={botId}
|
||||
onClear={async () => {
|
||||
await onDelete(i.keyword || '');
|
||||
}}
|
||||
afterUpdate={getKvList}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={s.update_time}
|
||||
data-dtestid={`${BotE2e.BotVariableDebugModalEditDateText}.${i.keyword}`}
|
||||
>
|
||||
{i.update_time
|
||||
? formatDate(Number(i.update_time), 'YYYY-MM-DD HH:mm')
|
||||
: ''}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Spin>
|
||||
<div
|
||||
className={s['variable-debug-footer']}
|
||||
data-testid={BotE2e.BotVariableDebugModalResetBtn}
|
||||
>
|
||||
<Tooltip
|
||||
className={s['hover-tip']}
|
||||
showArrow
|
||||
content={I18n.t('variable_reset_tips')}
|
||||
>
|
||||
<UIButton
|
||||
type="tertiary"
|
||||
onClick={() => {
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
bot_id: botId,
|
||||
resource_type: 'variable',
|
||||
action: 'reset',
|
||||
source: 'bot_detail_page',
|
||||
source_detail: 'memory_preview',
|
||||
});
|
||||
setShowResetModal(true);
|
||||
}}
|
||||
>
|
||||
{I18n.t('variable_reset')}
|
||||
</UIButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
zIndex={9999}
|
||||
centered
|
||||
okType="danger"
|
||||
visible={showResetModal}
|
||||
onCancel={() => {
|
||||
setShowResetModal(false);
|
||||
}}
|
||||
title={I18n.t('variable_reset_confirm')}
|
||||
okText={I18n.t('variable_reset_yes')}
|
||||
cancelText={I18n.t('variable_reset_no')}
|
||||
keepDOM={false}
|
||||
maskClosable={false}
|
||||
icon={
|
||||
<IconAlertCircle size="extra-large" style={{ color: '#FF2710' }} />
|
||||
}
|
||||
onOk={async () => {
|
||||
await onDelete();
|
||||
setShowResetModal(false);
|
||||
}}
|
||||
>
|
||||
{I18n.t('variable_reset_tips')}
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import { sendTeaEvent, EVENT_NAMES } from '@coze-arch/bot-tea';
|
||||
|
||||
export const useSendTeaEventForMemoryDebug = (p: { isStore: boolean }) => {
|
||||
const { isStore = false } = p;
|
||||
// TODO@XML 看起来在商店也用到了,先不改
|
||||
const params = useParams<DynamicParams>();
|
||||
const { bot_id = '', product_id = '' } = params;
|
||||
|
||||
const resourceTypeMaps = {
|
||||
longTimeMemory: 'long_term_memory',
|
||||
database: 'database',
|
||||
variable: 'variable',
|
||||
filebox: 'filebox',
|
||||
};
|
||||
|
||||
return (type: string, extraParams: Record<string, unknown> = {}) => {
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
bot_id: isStore ? product_id : bot_id,
|
||||
product_id: isStore ? product_id : '',
|
||||
resource_type: resourceTypeMaps[type || ''],
|
||||
action: 'turn_on',
|
||||
source: isStore ? 'store_detail_page' : 'bot_detail_page',
|
||||
source_detail: 'memory_preview',
|
||||
...extraParams,
|
||||
});
|
||||
};
|
||||
};
|
||||
30
frontend/packages/data/memory/database/src/index.ts
Normal file
30
frontend/packages/data/memory/database/src/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 { useMemoryDebugModal } from './components/memory-debug-modal';
|
||||
|
||||
export { VariableDebug } from './components/variable-debug';
|
||||
export { DatabaseDebug } from './components/database-debug';
|
||||
// export { FileBoxList } from './components/filebox-list';
|
||||
|
||||
export { MemoryDebugDropdown } from './components/memory-debug-dropdown';
|
||||
|
||||
export { MemoryModule, MemoryDebugDropdownMenuItem } from './types';
|
||||
export { useSendTeaEventForMemoryDebug } from './hooks/use-send-tea-event-for-memory-debug';
|
||||
|
||||
export { default as MultiDataTable } from './components/database-debug/multi-table';
|
||||
export { type DataTableRef } from './components/database-debug/table';
|
||||
export { type UseBotStore } from './components/filebox-list/types';
|
||||
31
frontend/packages/data/memory/database/src/types.ts
Normal file
31
frontend/packages/data/memory/database/src/types.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 ReactNode } from 'react';
|
||||
|
||||
export enum MemoryModule {
|
||||
Variable = 'variable',
|
||||
Database = 'database',
|
||||
LongTermMemory = 'longTermMemory',
|
||||
Filebox = 'filebox',
|
||||
}
|
||||
|
||||
export interface MemoryDebugDropdownMenuItem {
|
||||
label: string;
|
||||
name: MemoryModule;
|
||||
icon: ReactNode;
|
||||
component: ReactNode;
|
||||
}
|
||||
28
frontend/packages/data/memory/database/src/typings.d.ts
vendored
Normal file
28
frontend/packages/data/memory/database/src/typings.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare module '*.less' {
|
||||
const resource: { [key: string]: string };
|
||||
export = resource;
|
||||
}
|
||||
|
||||
declare const IS_BOE: boolean;
|
||||
declare const IS_DEV_MODE: boolean;
|
||||
declare const IS_OVERSEA: boolean;
|
||||
declare const IS_OVERSEA_RELEASE: boolean;
|
||||
declare const IS_PPE: boolean;
|
||||
declare const IS_PROD: boolean;
|
||||
declare const IS_RELEASE_VERSION: boolean;
|
||||
20
frontend/packages/data/memory/database/src/utils/index.ts
Normal file
20
frontend/packages/data/memory/database/src/utils/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const formatDate = (v: number, template = 'YYYY/MM/DD HH:mm:ss') =>
|
||||
dayjs.unix(v).format(template);
|
||||
21
frontend/packages/data/memory/database/tailwind.config.ts
Normal file
21
frontend/packages/data/memory/database/tailwind.config.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
export default {
|
||||
content: ['./src/**/*.{ts,tsx}', '../../packages/**/src/**/*.{ts,tsx}'],
|
||||
} satisfies Config;
|
||||
82
frontend/packages/data/memory/database/tsconfig.build.json
Normal file
82
frontend/packages/data/memory/database/tsconfig.build.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"compilerOptions": {
|
||||
"types": [],
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../arch/bot-api/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-error/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-flags/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-tea/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-typings/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/i18n/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/report-events/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../common/chat-area/chat-area/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../common/chat-area/chat-core/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../common/chat-area/plugin-message-grab/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../common/e2e/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../common/reporter/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": "../../knowledge/knowledge-resource-processor-base/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../knowledge/knowledge-resource-processor-core/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/stores/bot-detail/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/user-store/tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
frontend/packages/data/memory/database/tsconfig.json
Normal file
15
frontend/packages/data/memory/database/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.misc.json"
|
||||
}
|
||||
],
|
||||
"exclude": ["**/*"]
|
||||
}
|
||||
16
frontend/packages/data/memory/database/tsconfig.misc.json
Normal file
16
frontend/packages/data/memory/database/tsconfig.misc.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
}
|
||||
25
frontend/packages/data/memory/database/vitest.config.ts
Normal file
25
frontend/packages/data/memory/database/vitest.config.ts
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.
|
||||
*/
|
||||
|
||||
import { defineConfig } from '@coze-arch/vitest-config';
|
||||
|
||||
export default defineConfig({
|
||||
dirname: __dirname,
|
||||
preset: 'web',
|
||||
test: {
|
||||
environment: 'happy-dom',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user