feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
@@ -0,0 +1,5 @@
|
||||
const { defineConfig } = require('@coze-arch/stylelint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
extends: [],
|
||||
});
|
||||
63
frontend/packages/data/memory/database-creator/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# @coze-data/database-creator
|
||||
|
||||
create or edit database info
|
||||
|
||||
## Overview
|
||||
|
||||
This package is part of the Coze Studio monorepo and provides data management functionality. It serves as a core component in the Coze ecosystem.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installation
|
||||
|
||||
Add this package to your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@coze-data/database-creator": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
rush update
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```typescript
|
||||
import { /* exported functions/components */ } from '@coze-data/database-creator';
|
||||
|
||||
// Example usage
|
||||
// TODO: Add specific usage examples
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Core functionality for Coze Studio
|
||||
- TypeScript support
|
||||
- Modern ES modules
|
||||
|
||||
## API Reference
|
||||
|
||||
Please refer to the TypeScript definitions for detailed API documentation.
|
||||
|
||||
## Development
|
||||
|
||||
This package is built with:
|
||||
|
||||
- TypeScript
|
||||
- Modern JavaScript
|
||||
- Vitest for testing
|
||||
- ESLint for code quality
|
||||
|
||||
## Contributing
|
||||
|
||||
This package is part of the Coze Studio monorepo. Please follow the monorepo contribution guidelines.
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { expect, vi, describe, test } from 'vitest';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
|
||||
import { DatabaseModal } from '../src/components/database-modal';
|
||||
|
||||
vi.mock('@coze-arch/i18n', () => ({
|
||||
I18n: {
|
||||
t: (key: string) => key,
|
||||
},
|
||||
getUnReactiveLanguage: () => 'zh-CN',
|
||||
}));
|
||||
|
||||
const global = vi.hoisted(() => ({
|
||||
isApiError: vi.fn().mockReturnValue(false),
|
||||
useFlags: vi.fn().mockReturnValue([
|
||||
{
|
||||
'bot.data.excel_to_database': false,
|
||||
},
|
||||
]),
|
||||
mockProps: {
|
||||
visible: true,
|
||||
onCancel: vi.fn(),
|
||||
expertModeConfig: {
|
||||
isExpertMode: true,
|
||||
maxColumnNum: 10,
|
||||
maxTableNum: 2,
|
||||
readAndWriteModes: [1],
|
||||
},
|
||||
database: {
|
||||
tableId: '1',
|
||||
name: 'test',
|
||||
desc: 'test',
|
||||
readAndWriteMode: 1,
|
||||
tableMemoryList: [],
|
||||
},
|
||||
botId: 'bot-1',
|
||||
spaceId: 'space-1',
|
||||
readonly: false,
|
||||
NL2DBInfo: {
|
||||
prompt: 'test-nl',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-semi', async () => {
|
||||
const actual: object = await vi.importActual('@coze-arch/bot-semi');
|
||||
const { MockPopover } = (await vi.importActual(
|
||||
'./mock-components/mock-popover.tsx',
|
||||
)) as any;
|
||||
const { MockTextArea } = (await vi.importActual(
|
||||
'./mock-components/mock-textarea.tsx',
|
||||
)) as any;
|
||||
const { MockPopconfirm } = (await vi.importActual(
|
||||
'./mock-components/mock-popconfirm',
|
||||
)) as any;
|
||||
const { MockTooltip } = (await vi.importActual(
|
||||
'./mock-components/mock-tooltip',
|
||||
)) as any;
|
||||
return {
|
||||
...actual,
|
||||
Popover: MockPopover,
|
||||
TextArea: MockTextArea,
|
||||
Popconfirm: MockPopconfirm,
|
||||
ToolTip: MockTooltip,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@coze-arch/report-events', () => ({
|
||||
REPORT_EVENTS: {
|
||||
DatabaseNL2DB: 'DatabaseNL2DB',
|
||||
DatabaseAddTable: 'DatabaseAddTable',
|
||||
DatabaseAlterTable: 'DatabaseAlterTable',
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-tea', () => ({
|
||||
sendTeaEvent: vi.fn(),
|
||||
EVENT_NAMES: {
|
||||
generate_with_ai_click: 'generate_with_ai_click',
|
||||
nl2table_create_table_click: 'nl2table_create_table_click',
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-api', () => ({
|
||||
MemoryApi: {
|
||||
RecommendDataModel: () =>
|
||||
Promise.resolve({
|
||||
bot_table_list: [
|
||||
{
|
||||
type: 1,
|
||||
table_name: 'test',
|
||||
table_desc: 'test',
|
||||
field_list: [
|
||||
{
|
||||
name: 'name',
|
||||
desc: 'pokemon name',
|
||||
must_required: true,
|
||||
type: 1,
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
name: 'score',
|
||||
desc: 'pokemon score',
|
||||
must_required: false,
|
||||
type: 1,
|
||||
id: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-data/reporter', () => ({
|
||||
DataNamespace: {
|
||||
DATABASE: 'database',
|
||||
},
|
||||
dataReporter: {
|
||||
errorEvent: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-http', () => ({
|
||||
isApiError: global.isApiError,
|
||||
}));
|
||||
|
||||
vi.mock('@coze-studio/components', () => ({
|
||||
PopoverContent: (props: { children: React.ReactElement }) => props.children,
|
||||
}));
|
||||
|
||||
vi.mock('@douyinfe/semi-icons', async () => {
|
||||
const actual = await vi.importActual('@douyinfe/semi-icons');
|
||||
return {
|
||||
...(actual as any),
|
||||
IconAlertTriangle: () => <>IconAlertTriangle</>,
|
||||
IconClose: () => <>IconClose</>,
|
||||
IconChevronDown: () => <>IconChevronDown</>,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@coze-arch/bot-icons', () => ({
|
||||
IconAdd: () => <>IconAdd</>,
|
||||
IconWaringRed: () => <>IconAdd</>,
|
||||
IconDeleteOutline: () => <>IconDeleteOutline</>,
|
||||
IconWarningSize24: () => <>IconWarningSize24</>,
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-flags', () => ({
|
||||
useFlags: global.useFlags,
|
||||
}));
|
||||
|
||||
describe('database modal test', () => {
|
||||
test('render', async () => {
|
||||
await render(<DatabaseModal {...global.mockProps} />);
|
||||
const title = await screen.queryByText('db_edit_title');
|
||||
expect(title).not.toBeNull();
|
||||
});
|
||||
|
||||
test('show entry', async () => {
|
||||
global.mockProps = {
|
||||
...global.mockProps,
|
||||
NL2DBInfo: undefined as any,
|
||||
database: {
|
||||
...global.mockProps.database,
|
||||
tableId: '',
|
||||
},
|
||||
};
|
||||
await render(<DatabaseModal {...global.mockProps} />);
|
||||
const entry = await screen.queryByText('db_add_table_cust');
|
||||
expect(entry).not.toBeNull();
|
||||
});
|
||||
|
||||
test('show ai generate', async () => {
|
||||
global.mockProps.NL2DBInfo = {
|
||||
prompt: 'test',
|
||||
};
|
||||
global.mockProps.database.tableId = '';
|
||||
await render(<DatabaseModal {...global.mockProps} />);
|
||||
const aiCreateButton = await screen.queryByText('bot_database_ai_create');
|
||||
expect(aiCreateButton).not.toBeNull();
|
||||
if (aiCreateButton) {
|
||||
await fireEvent.click(aiCreateButton);
|
||||
}
|
||||
const aiGenerateButton = await screen.queryByText(
|
||||
'bot_database_ai_generate',
|
||||
);
|
||||
|
||||
expect(aiGenerateButton).not.toBeNull();
|
||||
});
|
||||
|
||||
test('generate table by nl', async () => {
|
||||
global.mockProps.NL2DBInfo = {
|
||||
prompt: 'test',
|
||||
};
|
||||
global.mockProps.database.tableId = '';
|
||||
await render(<DatabaseModal {...global.mockProps} />);
|
||||
const aiCreateButton = await screen.queryByText('bot_database_ai_create');
|
||||
expect(aiCreateButton).not.toBeNull();
|
||||
if (aiCreateButton) {
|
||||
await fireEvent.click(aiCreateButton);
|
||||
}
|
||||
const nlInput = await screen.queryByRole('mock-textarea');
|
||||
expect(nlInput).not.toBeNull();
|
||||
if (nlInput) {
|
||||
await fireEvent.input(nlInput, {
|
||||
target: {
|
||||
value: 'a pokemon table, name and score',
|
||||
},
|
||||
});
|
||||
}
|
||||
const aiGenerateButton = await screen.queryByText(
|
||||
'bot_database_ai_generate',
|
||||
);
|
||||
|
||||
expect(aiGenerateButton).not.toBeNull();
|
||||
if (aiGenerateButton) {
|
||||
await fireEvent.click(aiGenerateButton);
|
||||
}
|
||||
const content = await screen.queryByText('test');
|
||||
expect(content).not.toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -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 React from 'react';
|
||||
|
||||
export const mockBanner = (props: { description: string }) => (
|
||||
<>{props.description}</>
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const MockButton = (props: {
|
||||
children: React.ReactElement;
|
||||
onClick: () => void;
|
||||
}) => <button onClick={props.onClick}>{props.children}</button>;
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const MockDivider = () => <>divider</>;
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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 @typescript-eslint/no-explicit-any */
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
class EventEmitter {
|
||||
private listenerMap: Map<string, Array<(...args: any[]) => void>>;
|
||||
constructor() {
|
||||
this.listenerMap = new Map();
|
||||
}
|
||||
fire(key: string, ...rest: any[]) {
|
||||
const listeners = this.listenerMap.get(key);
|
||||
for (const listener of listeners) {
|
||||
listener(rest);
|
||||
}
|
||||
}
|
||||
on(key: string, listener: (...args: any[]) => void) {
|
||||
if (!this.listenerMap.has(key)) {
|
||||
this.listenerMap.set(key, []);
|
||||
}
|
||||
const listeners = this.listenerMap.get(key);
|
||||
listeners.push(listener);
|
||||
}
|
||||
off(key: string) {
|
||||
this.listenerMap.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
const eventEmitter = new EventEmitter();
|
||||
|
||||
let initValue = {};
|
||||
|
||||
export const withField = (
|
||||
Comp: (props: { onChange: (v: any) => void }) => React.ReactElement,
|
||||
field: {
|
||||
valueKey: string;
|
||||
},
|
||||
) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks -- linter-disable-autofix
|
||||
const onChange = useCallback(v => {
|
||||
eventEmitter.fire('change', field.valueKey, v);
|
||||
}, []);
|
||||
return (props: {
|
||||
field: string;
|
||||
label: {
|
||||
text: string;
|
||||
};
|
||||
validate: (v) => string | undefined;
|
||||
}) => {
|
||||
<>
|
||||
{props.label.text}
|
||||
<Comp
|
||||
onChange={v => {
|
||||
if (props.validate(v.target.value) !== undefined) {
|
||||
return;
|
||||
}
|
||||
onChange(v.target.value);
|
||||
}}
|
||||
/>
|
||||
</>;
|
||||
};
|
||||
};
|
||||
|
||||
export const MockForm = React.forwardRef(
|
||||
(
|
||||
props: {
|
||||
initValues: Record<string, any>;
|
||||
children: React.ReactElement;
|
||||
onValueChange: (values: any, changeV: any) => void;
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const valuesRef = useRef(props.initValues);
|
||||
useImperativeHandle(ref, () => ({
|
||||
formApi: {
|
||||
validate: (_fields: string[]) => true,
|
||||
},
|
||||
}));
|
||||
useEffect(() => {
|
||||
eventEmitter.on('change', (...args: any[]) => {
|
||||
// 实际上都是假的咯 假设 args 第一个参数是 field 第二个参数是具体 value
|
||||
valuesRef.current[args[0]] = args[1];
|
||||
props.onValueChange(valuesRef.current, args[1]);
|
||||
});
|
||||
return () => {
|
||||
eventEmitter.off('change');
|
||||
};
|
||||
}, []);
|
||||
initValue = props.initValues;
|
||||
return <>{props.children}</>;
|
||||
},
|
||||
);
|
||||
|
||||
(MockForm as any).TextArea = (props: { field: string; label: string }) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks -- linter-disable-autofix
|
||||
const [value, setValue] = useState(initValue[props.field]);
|
||||
return (
|
||||
<>
|
||||
<div>{props.label}</div>
|
||||
<div>{initValue[props.field]}</div>
|
||||
<input
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v.target.value);
|
||||
eventEmitter.fire('change', props.field, v.target.value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
(MockForm as any).Select = (props: {
|
||||
field: string;
|
||||
label: {
|
||||
text: string;
|
||||
extra: React.ReactElement;
|
||||
};
|
||||
optionList: Array<{
|
||||
label: React.ReactElement;
|
||||
value: number;
|
||||
}>;
|
||||
}) => {
|
||||
const { label, optionList } = props;
|
||||
return (
|
||||
<>
|
||||
<div>{label.text}</div>
|
||||
{label.extra}
|
||||
{optionList.map(option => (
|
||||
<button
|
||||
onClick={() => {
|
||||
eventEmitter.fire('change', props.field, option.value);
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const Icon = (props: { svg: React.ReactElement }) => <>{props.svg}</>;
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const MockImage = (props: { src: string }) => <img src={props.src} />;
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const Modal = (props: {
|
||||
visible: boolean;
|
||||
onOk: () => void;
|
||||
onCancel: () => void;
|
||||
okText: string;
|
||||
cancelText: string;
|
||||
title: React.ReactElement;
|
||||
children: React.ReactElement;
|
||||
}) => {
|
||||
if (!props.visible) {
|
||||
return <>no visible</>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div>{props.title}</div>
|
||||
{props.children}
|
||||
<div>
|
||||
<button onClick={props.onCancel}>{props.cancelText}</button>
|
||||
<button onClick={props.onOk}>{props.okText}</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const MockPopConfirm = (props: {
|
||||
title: string;
|
||||
content: string;
|
||||
okText: string;
|
||||
cancelText: string;
|
||||
onConfirm: () => void;
|
||||
children: React.ReactElement;
|
||||
}) => {
|
||||
const { title, content, okText, cancelText, onConfirm, children } = props;
|
||||
return (
|
||||
<>
|
||||
<div>{title}</div>
|
||||
<div>{content}</div>
|
||||
{children}
|
||||
<button onClick={onConfirm}>{okText}</button>
|
||||
<button>{cancelText}</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const MockPopover = (props: {
|
||||
content: React.ReactElement;
|
||||
children: React.ReactElement;
|
||||
visible: boolean;
|
||||
}) => (
|
||||
<>
|
||||
{props.visible ? (
|
||||
<div>
|
||||
{props.content}
|
||||
{props.children}
|
||||
</div>
|
||||
) : (
|
||||
<div>{props.children}</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const mockSpin = () => <>spin</>;
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const MockSwitch = (props: {
|
||||
checked: boolean;
|
||||
onChange: (v: boolean) => void;
|
||||
}) => {
|
||||
const { checked, onChange } = props;
|
||||
return (
|
||||
<button onClick={() => onChange(!checked)}>
|
||||
{checked ? 'switch on' : 'switch off'}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const MockUITableAction = (props: {
|
||||
deleteProps: {
|
||||
handleClick: () => void;
|
||||
tooltip: {
|
||||
content: string;
|
||||
};
|
||||
};
|
||||
}) => {
|
||||
const { handleClick, tooltip } = props.deleteProps;
|
||||
return <button onClick={handleClick}>{tooltip.content}</button>;
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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, { useImperativeHandle, useState } from 'react';
|
||||
|
||||
export const MockTextArea = React.forwardRef(
|
||||
(
|
||||
props: {
|
||||
placeHolder: string;
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { placeHolder } = props;
|
||||
const [value, setValue] = useState('');
|
||||
useImperativeHandle(ref, () => ({
|
||||
value,
|
||||
}));
|
||||
return (
|
||||
<input
|
||||
placeholder={placeHolder}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v.target.value);
|
||||
}}
|
||||
role="mock-textarea"
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const Toast = {
|
||||
error: () => {},
|
||||
waring: () => {},
|
||||
success: () => {},
|
||||
info: () => {},
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const MockTooltip = (props: {
|
||||
children: React.ReactElement;
|
||||
content: string;
|
||||
}) => (
|
||||
<>
|
||||
<div>{props.content}</div>
|
||||
{props.children}
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const MockUIButton = (props: {
|
||||
children: React.ReactElement;
|
||||
onClick: () => void;
|
||||
}) => <button onClick={props.onClick}>{props.children}</button>;
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const MockUITable = (props: {
|
||||
tableProps: {
|
||||
columns: {
|
||||
dataIndex: string;
|
||||
title: React.ReactElement;
|
||||
render: (text, record, index) => React.ReactElement;
|
||||
}[];
|
||||
dataSource: Record<string, any>[];
|
||||
};
|
||||
}) => {
|
||||
const { columns, dataSource } = props.tableProps;
|
||||
|
||||
return (
|
||||
<>
|
||||
{columns.map(column => {
|
||||
const { title, dataIndex, render } = column;
|
||||
return (
|
||||
<div key={dataIndex}>
|
||||
{title}
|
||||
{dataSource.map((data, index) => render(undefined, data, index))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
mock 以下组件
|
||||
UITable,
|
||||
UITableAction,
|
||||
Button,
|
||||
Switch,
|
||||
Tooltip,
|
||||
Image,
|
||||
Form,
|
||||
Banner,
|
||||
withField,
|
||||
Popover,
|
||||
Spin,
|
||||
Icon,
|
||||
Popconfirm,
|
||||
Divider,
|
||||
TextArea,
|
||||
UIButton,
|
||||
Modal,
|
||||
Toast,
|
||||
@@ -0,0 +1 @@
|
||||
注意这轮单测不包括 database-create-from-excel 因为这个文件夹下的内容是废弃内容 by 张园洲
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"operationSettings": [
|
||||
{
|
||||
"operationName": "test:cov",
|
||||
"outputFolderNames": ["coverage"]
|
||||
},
|
||||
{
|
||||
"operationName": "ts-check",
|
||||
"outputFolderNames": ["./dist"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
const { defineConfig } = require('@coze-arch/eslint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
packageRoot: __dirname,
|
||||
preset: 'web',
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'max-lines': 'off',
|
||||
'max-lines-per-function': 'off',
|
||||
'@coze-arch/max-line-per-function': 'off',
|
||||
'@typescript-eslint/no-magic-numbers': 'off',
|
||||
'@typescript-eslint/naming-convention': 'off',
|
||||
'@coze-arch/no-deep-relative-import': 'warn',
|
||||
'@typescript-eslint/no-namespace': [
|
||||
'error',
|
||||
{
|
||||
allowDeclarations: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
59
frontend/packages/data/memory/database-creator/package.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@coze-data/database-creator",
|
||||
"version": "0.0.1",
|
||||
"description": "create or edit database info",
|
||||
"license": "Apache-2.0",
|
||||
"author": "zhangyuanzhou.zyz@bytedance.com",
|
||||
"maintainers": [
|
||||
"liaoxing.lx@bytedance.com"
|
||||
],
|
||||
"main": "src/index.tsx",
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"lint": "eslint ./ --cache",
|
||||
"test": "vitest --run --passWithNoTests",
|
||||
"test:cov": "npm run test -- --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coze-arch/bot-api": "workspace:*",
|
||||
"@coze-arch/bot-http": "workspace:*",
|
||||
"@coze-arch/bot-icons": "workspace:*",
|
||||
"@coze-arch/bot-semi": "workspace:*",
|
||||
"@coze-arch/i18n": "workspace:*",
|
||||
"@coze-arch/report-events": "workspace:*",
|
||||
"@coze-data/e2e": "workspace:*",
|
||||
"@coze-data/reporter": "workspace:*",
|
||||
"@coze-studio/bot-detail-store": "workspace:*",
|
||||
"@coze-studio/components": "workspace:*",
|
||||
"@douyinfe/semi-icons": "^2.36.0",
|
||||
"ahooks": "^3.7.8",
|
||||
"classnames": "^2.3.2",
|
||||
"immer": "^10.0.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^4.0.2",
|
||||
"react-markdown": "^8.0.3",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coze-arch/bot-flags": "workspace:*",
|
||||
"@coze-arch/bot-tea": "workspace:*",
|
||||
"@coze-arch/bot-typings": "workspace:*",
|
||||
"@coze-arch/eslint-config": "workspace:*",
|
||||
"@coze-arch/stylelint-config": "workspace:*",
|
||||
"@coze-arch/ts-config": "workspace:*",
|
||||
"@coze-arch/vitest-config": "workspace:*",
|
||||
"@testing-library/jest-dom": "^6.1.5",
|
||||
"@testing-library/react": "^14.1.2",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@types/lodash-es": "^4.17.10",
|
||||
"@types/react": "18.2.37",
|
||||
"@types/react-dom": "18.2.15",
|
||||
"@vitest/coverage-v8": "~3.0.5",
|
||||
"react": "~18.2.0",
|
||||
"react-dom": "~18.2.0",
|
||||
"vite-plugin-svgr": "~3.3.0",
|
||||
"vitest": "~3.0.5"
|
||||
},
|
||||
"// deps": "immer@^10.0.3 为脚本自动补齐,请勿改动"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="current" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Import Excel/CSV">
|
||||
<g id="Union">
|
||||
<path
|
||||
d="M7 13C7 12.4477 7.44772 12 8 12H16C16.5523 12 17 12.4477 17 13C17 13.5523 16.5523 14 16 14H8C7.44772 14 7 13.5523 7 13Z" />
|
||||
<path
|
||||
d="M7 18C7 17.4477 7.44772 17 8 17H13C13.5523 17 14 17.4477 14 18C14 18.5523 13.5523 19 13 19H8C7.44772 19 7 18.5523 7 18Z" />
|
||||
<path
|
||||
d="M5 1.5C3.89543 1.5 3 2.39543 3 3.5V22.5C3 23.6046 3.89543 24.5 5 24.5H19C20.1046 24.5 21 23.6046 21 22.5V8.05251C21 7.53746 20.8013 7.04227 20.4453 6.67007L16.0907 2.11755C15.7134 1.7231 15.1913 1.5 14.6454 1.5H5ZM5 22.5V3.5L14.5 3.5V7C14.5 7.55228 14.9477 8 15.5 8H18.9498L19 8.05252V22.5H5Z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 783 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.3775 2.01277L14.4504 4.08571C15.0228 4.65813 15.0228 5.58622 14.4504 6.15864L6.15864 14.4504C5.58622 15.0228 4.65813 15.0228 4.08571 14.4504L2.01277 12.3775C1.44034 11.805 1.44034 10.8769 2.01277 10.3045L10.3045 2.01277C10.8769 1.44034 11.805 1.44034 12.3775 2.01277ZM6.11644 0.841895C6.18123 0.637199 6.47089 0.637199 6.53568 0.841895L6.74973 1.51819C6.77133 1.58642 6.82478 1.63987 6.89301 1.66146L7.5693 1.87552C7.774 1.94031 7.774 2.22997 7.5693 2.29476L6.89301 2.50881C6.82478 2.53041 6.77133 2.58386 6.74973 2.65208L6.53568 3.32838C6.47089 3.53308 6.18123 3.53308 6.11644 3.32838L5.90239 2.65208C5.88079 2.58386 5.82734 2.53041 5.75912 2.50881L5.08282 2.29476C4.87812 2.22997 4.87812 1.94031 5.08282 1.87552L5.75912 1.66146C5.82734 1.63987 5.88079 1.58642 5.90239 1.51819L6.11644 0.841895ZM1.85997 3.23387L2.22567 2.16297C2.30246 1.9381 2.6205 1.9381 2.6973 2.16297L3.06299 3.23387C3.08793 3.30688 3.14527 3.36423 3.21828 3.38916L4.28918 3.75486C4.51406 3.83165 4.51406 4.14969 4.28918 4.22648L3.21828 4.59218C3.14527 4.61711 3.08793 4.67446 3.06299 4.74747L2.6973 5.81837C2.6205 6.04324 2.30246 6.04324 2.22567 5.81837L1.85997 4.74747C1.83504 4.67446 1.7777 4.61711 1.70468 4.59218L0.633785 4.22648C0.40891 4.14969 0.40891 3.83165 0.633785 3.75486L1.70468 3.38916C1.7777 3.36423 1.83504 3.30688 1.85997 3.23387ZM13.4837 9.6851C13.5605 9.46022 13.8785 9.46022 13.9553 9.6851L14.321 10.756C14.3459 10.829 14.4033 10.8864 14.4763 10.9113L15.5472 11.277C15.7721 11.3538 15.7721 11.6718 15.5472 11.7486L14.4763 12.1143C14.4033 12.1392 14.3459 12.1966 14.321 12.2696L13.9553 13.3405C13.8785 13.5654 13.5605 13.5654 13.4837 13.3405L13.118 12.2696C13.093 12.1966 13.0357 12.1392 12.9627 12.1143L11.8918 11.7486C11.6669 11.6718 11.6669 11.3538 11.8918 11.277L12.9627 10.9113C13.0357 10.8864 13.093 10.829 13.118 10.756L13.4837 9.6851ZM10.5629 4.34447C10.9922 3.91515 11.6882 3.91515 12.1176 4.34447C12.5469 4.77379 12.5469 5.46985 12.1176 5.89917L10.0446 7.97211C9.6153 8.40143 8.91924 8.40143 8.48992 7.97211C8.0606 7.54279 8.0606 6.84673 8.48992 6.41741L10.5629 4.34447Z" fill="#4D53E8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="current" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="icon_add_outlined">
|
||||
<path id="Union"
|
||||
d="M12 2.5C11.4477 2.5 11 2.94772 11 3.5V11.5H3C2.44772 11.5 2 11.9477 2 12.5C2 13.0523 2.44772 13.5 3 13.5H11V21.5C11 22.0523 11.4477 22.5 12 22.5C12.5523 22.5 13 22.0523 13 21.5V13.5H21C21.5523 13.5 22 13.0523 22 12.5C22 11.9477 21.5523 11.5 21 11.5H13V3.5C13 2.94772 12.5523 2.5 12 2.5Z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 463 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M4.8149 6.5L11.1851 6.5C11.4362 6.5 11.586 6.73292 11.4467 6.90682L8.26158 10.8835C8.13714 11.0388 7.86286 11.0388 7.73842 10.8835L4.55333 6.90682C4.41405 6.73292 4.56381 6.5 4.8149 6.5Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 333 B |
@@ -0,0 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1848_33110)">
|
||||
<path d="M7.99996 14C11.3136 14 14 11.3137 14 7.99999C14 4.68632 11.3136 1.99999 7.99996 1.99999C4.68629 1.99999 1.99996 4.68632 1.99996 7.99999C1.99996 11.3137 4.68629 14 7.99996 14ZM7.99996 15.3333C3.94996 15.3333 0.666626 12.05 0.666626 7.99999C0.666626 3.94999 3.94996 0.666656 7.99996 0.666656C12.05 0.666656 15.3333 3.94999 15.3333 7.99999C15.3333 12.05 12.05 15.3333 7.99996 15.3333ZM7.3333 10.3333V7.66666C6.96511 7.66666 6.66663 7.36818 6.66663 6.99999C6.66663 6.6318 6.96511 6.33332 7.3333 6.33332H8.00237C8.36962 6.33332 8.66774 6.63033 8.6683 6.99758C8.66998 8.10948 8.66663 9.22144 8.66663 10.3333H8.99996C9.36815 10.3333 9.66663 10.6318 9.66663 11C9.66663 11.3682 9.36815 11.6667 8.99996 11.6667H6.99996C6.63177 11.6667 6.3333 11.3682 6.3333 11C6.3333 10.6318 6.63177 10.3333 6.99996 10.3333H7.3333ZM7.99996 5.66666C7.63177 5.66666 7.3333 5.36818 7.3333 4.99999C7.3333 4.6318 7.63177 4.33332 7.99996 4.33332C8.36815 4.33332 8.66663 4.6318 8.66663 4.99999C8.66663 5.36818 8.36815 5.66666 7.99996 5.66666Z" fill="#C6CACD"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1848_33110">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M11.1851 9.5L4.8149 9.5C4.56381 9.5 4.41405 9.26708 4.55333 9.09318L7.73842 5.11652C7.86286 4.96116 8.13714 4.96116 8.26158 5.11652L11.4467 9.09318C11.586 9.26708 11.4362 9.5 11.1851 9.5Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 334 B |
@@ -0,0 +1,11 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="icon_wiki-excel_colorful">
|
||||
<g id="Group 1321320754">
|
||||
<path id="Rectangle 2530" d="M4 4.0186C4 2.53559 5.19334 1.33337 6.66541 1.33337H19.4404C19.7939 1.33337 20.1328 1.47483 20.3828 1.72662L27.5983 8.99581C27.8482 9.2476 27.9886 9.5891 27.9886 9.94518V28.1856C27.9886 29.6686 26.7953 30.8708 25.3232 30.8708H6.66541C5.19334 30.8708 4 29.6686 4 28.1856V4.0186Z" fill="#32A645"/>
|
||||
<path id="csv" d="M12.5336 22.6041L12.5395 22.5629L12.5567 22.4425H12.4351H11.4262H11.3438L11.3238 22.5224L11.312 22.5696C11.2315 22.8919 11.0661 23.1596 10.8265 23.347C10.587 23.5342 10.267 23.6461 9.86853 23.6461C9.36786 23.6461 8.95012 23.4484 8.6554 23.0849C8.35894 22.7191 8.18038 22.177 8.18038 21.4801V21.4742C8.18038 20.7949 8.35456 20.2506 8.64815 19.8786C8.93993 19.5089 9.35476 19.3022 9.85673 19.3022C10.2858 19.3022 10.6109 19.4307 10.8462 19.626C11.0824 19.8221 11.2341 20.0904 11.3061 20.3787L11.3179 20.4259L11.3379 20.5058H11.4203H12.4233H12.5428L12.528 20.3873L12.5221 20.3402C12.3858 19.2377 11.4387 18.1826 9.85673 18.1826C8.97717 18.1826 8.23953 18.503 7.72274 19.0791C7.20694 19.6541 6.91917 20.4746 6.91917 21.4624V21.4683C6.91917 22.4641 7.2006 23.2892 7.71512 23.8668C8.23082 24.4457 8.97178 24.7657 9.86853 24.7657C11.3037 24.7657 12.3606 23.8281 12.5336 22.6041Z" fill="white" stroke="white" stroke-width="0.210982"/>
|
||||
<path id="csv_2" d="M13.5353 22.837L13.5354 22.8371C13.6003 23.4122 13.8628 23.8975 14.3012 24.2377C14.7384 24.5768 15.3426 24.7657 16.0828 24.7657C17.4813 24.7657 18.572 23.9883 18.572 22.8312V22.8253C18.572 22.369 18.454 21.9985 18.1623 21.7045C17.875 21.4148 17.4306 21.2111 16.804 21.0589L16.8039 21.0589L15.8305 20.823C15.8305 20.823 15.8305 20.8229 15.8304 20.8229C15.4997 20.7424 15.2808 20.6347 15.1455 20.5072C15.0143 20.3835 14.9552 20.2351 14.9552 20.0522V20.0463C14.9552 19.808 15.0547 19.6134 15.2365 19.4754C15.4211 19.3354 15.6973 19.2491 16.0533 19.2491C16.4159 19.2491 16.695 19.3431 16.8913 19.4985C17.0869 19.6532 17.2089 19.8754 17.2469 20.1493L17.2469 20.1498L17.2528 20.1911L17.2658 20.2816H17.3573H18.3367H18.4506L18.4419 20.168L18.436 20.0916C18.436 20.0915 18.436 20.0915 18.436 20.0915C18.3544 18.9994 17.4427 18.1826 16.0533 18.1826C14.6903 18.1826 13.694 18.9588 13.694 20.0935V20.0994C13.694 20.5669 13.8432 20.9506 14.1504 21.2522C14.4542 21.5505 14.9042 21.7603 15.4917 21.9012C15.4918 21.9012 15.4918 21.9013 15.4918 21.9013L16.4648 22.1371C16.4648 22.1371 16.4649 22.1372 16.465 22.1372C16.8074 22.2213 17.0169 22.3206 17.1412 22.4397C17.2598 22.5535 17.3108 22.6954 17.3108 22.8961V22.902C17.3108 23.1432 17.2051 23.3369 17.0048 23.4744C16.8004 23.6146 16.4919 23.6992 16.0887 23.6992C15.684 23.6992 15.3847 23.6122 15.1742 23.4622C14.9656 23.3134 14.8336 23.0952 14.7761 22.8103L14.7643 22.7515L14.7474 22.6667H14.6609H13.6343H13.5164L13.5294 22.7839L13.5353 22.837Z" fill="white" stroke="white" stroke-width="0.210982"/>
|
||||
<path id="csv_3" d="M21.5641 24.5781L21.5895 24.6472H21.6631H22.7133H22.7869L22.8123 24.578L25.0662 18.4419L25.1183 18.3H24.9672H23.8992H23.8235L23.7993 18.3718L22.1883 23.1543L20.5831 18.3719L20.5589 18.3H20.483H19.4033H19.2521L19.3043 18.442L21.5641 24.5781Z" fill="white" stroke="white" stroke-width="0.210982"/>
|
||||
<path id="Rectangle 2531" opacity="0.8" d="M19.9802 1.97637C19.9802 1.73986 20.2666 1.62187 20.4332 1.78972L27.5282 8.93745C27.6941 9.10464 27.5757 9.38905 27.3401 9.38905H22.6456C21.1736 9.38905 19.9802 8.18683 19.9802 6.70382V1.97637Z" fill="#278B34"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="icon_wiki-excel_colorful">
|
||||
<path id="Rectangle 2530" d="M4 4.00004C4 2.52728 5.19391 1.33337 6.66667 1.33337H19.4477C19.8013 1.33337 20.1405 1.47385 20.3905 1.7239L27.6095 8.94285C27.8595 9.1929 28 9.53204 28 9.88566V28C28 29.4728 26.8061 30.6667 25.3333 30.6667H6.66667C5.19391 30.6667 4 29.4728 4 28V4.00004Z" fill="#32A645"/>
|
||||
<path id="Rectangle 2531" opacity="0.8" d="M20 1.97716C20 1.73959 20.2872 1.62061 20.4552 1.7886L27.5448 8.87815C27.7128 9.04614 27.5938 9.33337 27.3562 9.33337H22.6667C21.1939 9.33337 20 8.13947 20 6.66671V1.97716Z" fill="#258832"/>
|
||||
<path id="icon_file_excel_nor" d="M11.3955 13.2123H12.8513C12.9158 13.2123 12.9762 13.244 13.0129 13.2971L15.811 17.352L18.6239 13.2967C18.6606 13.2438 18.7208 13.2123 18.7852 13.2123H20.2408C20.3492 13.2123 20.4371 13.3002 20.4371 13.4086C20.4371 13.4495 20.4244 13.4894 20.4007 13.5226L16.7567 18.6342L20.6906 24.175C20.7534 24.2634 20.7326 24.386 20.6441 24.4488C20.6109 24.4723 20.5712 24.485 20.5305 24.485H19.0748C19.0103 24.485 18.95 24.4534 18.9133 24.4004L15.811 19.9165L12.7234 24.4C12.6868 24.4532 12.6263 24.485 12.5617 24.485H11.1058C10.9973 24.485 10.9094 24.3971 10.9094 24.2886C10.9094 24.2481 10.9219 24.2086 10.9453 24.1755L14.8499 18.6342L11.2351 13.522C11.1725 13.4335 11.1935 13.3109 11.2821 13.2483C11.3152 13.2249 11.3548 13.2123 11.3955 13.2123Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type Ref, forwardRef, type FC } from 'react';
|
||||
|
||||
import { type ButtonProps } from '@coze-arch/bot-semi/Button';
|
||||
import { Button } from '@coze-arch/bot-semi';
|
||||
|
||||
export type BotDebugButtonProps = ButtonProps & {
|
||||
readonly: boolean;
|
||||
};
|
||||
export const BotDebugButton: FC<BotDebugButtonProps> = forwardRef(
|
||||
(props: BotDebugButtonProps, ref: Ref<Button>) => {
|
||||
const { readonly, ...rest } = props;
|
||||
|
||||
if (readonly) {
|
||||
return null;
|
||||
}
|
||||
return <Button {...rest} ref={ref} />;
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.processing-tag-process {
|
||||
color: var(--Light-color-green---green-6, #32A247) !important;
|
||||
background: #D2F3D5 !important;
|
||||
}
|
||||
|
||||
.processing-tag-failed {
|
||||
color: var(--Light-color-red---red-6, #DB2E13) !important;
|
||||
background: #FFE0D2 !important;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
import { Tag, Tooltip } from '@coze-arch/bot-semi';
|
||||
|
||||
import { useUploadProgress } from '../../hooks/use-upload-progress';
|
||||
import { ImportFileTaskStatus } from '../../datamodel';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface ProcessingTagProps {
|
||||
tableID: string;
|
||||
botID: string;
|
||||
}
|
||||
export const ProcessingTag: FC<ProcessingTagProps> = props => {
|
||||
const { tableID, botID } = props;
|
||||
|
||||
const progressInfo = useUploadProgress({ tableID, botID });
|
||||
|
||||
if (progressInfo?.status === ImportFileTaskStatus.Enqueue) {
|
||||
return (
|
||||
<Tag className={styles['processing-tag-process']}>
|
||||
{I18n.t('db_table_0126_031')}: {progressInfo.progress}%
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
if (progressInfo?.status === ImportFileTaskStatus.Failed) {
|
||||
return (
|
||||
<Tooltip content={progressInfo?.errorMessage}>
|
||||
<Tag className={styles['processing-tag-failed']}>
|
||||
{I18n.t('db_table_0126_031')}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (progressInfo?.status === ImportFileTaskStatus.Succeed) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
.common-svg-icon(@size: 14px, @color: #3370ff) {
|
||||
>svg {
|
||||
width: @size;
|
||||
height: @size;
|
||||
|
||||
>path {
|
||||
fill: @color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 24px 0 17px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 22px;
|
||||
color: var(--light-usage-text-color-text-0, #1c1f23);
|
||||
}
|
||||
|
||||
.progress-success-icon {
|
||||
.common-svg-icon(20px, var(--semi-color-primary));
|
||||
}
|
||||
@@ -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 { useMemo, type FC } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconSvgUploadCompletedIcon } from '@coze-arch/bot-icons';
|
||||
|
||||
import { useStepStore } from '../../store/step';
|
||||
import { useInitialConfigStore } from '../../store/initial-config';
|
||||
import outerStyles from '../../index.module.less';
|
||||
import { useUploadProgress } from '../../hooks/use-upload-progress';
|
||||
import { ImportFileTaskStatus } from '../../datamodel';
|
||||
import { UploadProgress } from './upload-progress';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const Processing: FC = () => {
|
||||
const { currentState, tableStructure, upload } = useStepStore(state => ({
|
||||
currentState: state.step4_processing,
|
||||
setCurrentState: state.set_step4_processing,
|
||||
tableStructure: state.step2_tableStructure,
|
||||
upload: state.step1_upload,
|
||||
}));
|
||||
|
||||
const { botId } = useInitialConfigStore(state => ({
|
||||
botId: state.botId,
|
||||
}));
|
||||
|
||||
const { fileList } = upload;
|
||||
const { tableValue } = tableStructure;
|
||||
|
||||
const { tableID } = currentState;
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const progressInfo = useUploadProgress({ tableID, botID: botId });
|
||||
|
||||
const headerTitle = useMemo(() => {
|
||||
let msg: string = I18n.t('db_table_0126_029');
|
||||
if (progressInfo?.status === ImportFileTaskStatus.Succeed) {
|
||||
msg = I18n.t('datasets_createFileModel_step4_Finish');
|
||||
} else if (progressInfo?.status === ImportFileTaskStatus.Failed) {
|
||||
msg = I18n.t('datasets_createFileModel_step4_failed');
|
||||
}
|
||||
return msg;
|
||||
}, [progressInfo?.status]);
|
||||
|
||||
return (
|
||||
<div className={outerStyles.stepWrapper}>
|
||||
<div className={styles.text}>{headerTitle}</div>
|
||||
<div className={styles['progress-list']}>
|
||||
<UploadProgress
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
key={fileList[0].response.upload_uri}
|
||||
className={styles['dataset-progress']}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
text={tableValue.name}
|
||||
percent={progressInfo?.progress || 0}
|
||||
status={progressInfo?.status || ImportFileTaskStatus.Enqueue}
|
||||
statusDesc={''}
|
||||
format={percent =>
|
||||
percent < 100 ? (
|
||||
`${percent}%`
|
||||
) : (
|
||||
<IconSvgUploadCompletedIcon
|
||||
className={styles['progress-success-icon']}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
.dataset-progress-wrap {
|
||||
position: relative;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.dataset-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
|
||||
.dataset-progress-content {
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
|
||||
background-color: rgba(46, 50, 56, 5%);
|
||||
border-radius: 8px;
|
||||
|
||||
.progress-bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
height: 32px;
|
||||
|
||||
background-color: var(--semi-color-primary);
|
||||
border-radius: 8px;
|
||||
|
||||
transition: width linear 300ms;
|
||||
}
|
||||
|
||||
.text {
|
||||
box-sizing: border-box;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 32px;
|
||||
word-break: keep-all;
|
||||
}
|
||||
}
|
||||
|
||||
.dataset-progress-format {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
box-sizing: border-box;
|
||||
width: 48px;
|
||||
padding-right: 6px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: rgba(28, 31, 35, 60%);
|
||||
}
|
||||
}
|
||||
|
||||
.dataset-progress-error {
|
||||
position: absolute;
|
||||
top: 33px;
|
||||
left: 4px;
|
||||
|
||||
font-size: 12px;
|
||||
color: rgba(255, 39, 16, 100%)
|
||||
}
|
||||
}
|
||||
|
||||
.dataset-progress-wrap:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 { Typography } from '@coze-arch/bot-semi';
|
||||
|
||||
import { ImportFileTaskStatus } from '../../../datamodel';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface DatasetProgressProps {
|
||||
className?: string | undefined;
|
||||
style?: React.CSSProperties;
|
||||
text: string;
|
||||
percent: number;
|
||||
format?: (percent: number) => React.ReactNode;
|
||||
status?: ImportFileTaskStatus;
|
||||
statusDesc?: string;
|
||||
}
|
||||
|
||||
export const UploadProgress: React.FC<DatasetProgressProps> = ({
|
||||
className,
|
||||
style,
|
||||
text,
|
||||
percent,
|
||||
format,
|
||||
status,
|
||||
statusDesc,
|
||||
}) => {
|
||||
const SUCCESSIVE_PROCESSING = 100;
|
||||
|
||||
return (
|
||||
<div className={`${s['dataset-progress-wrap']}`}>
|
||||
<div className={`${s['dataset-progress']} ${className}`} style={style}>
|
||||
<div className={s['dataset-progress-content']}>
|
||||
<Typography.Text
|
||||
className={s.text}
|
||||
strong
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: { content: text },
|
||||
},
|
||||
}}
|
||||
style={{ color: '#4D53E8' }}
|
||||
>
|
||||
{text}
|
||||
</Typography.Text>
|
||||
<div className={s['progress-bar']} style={{ width: `${percent}%` }}>
|
||||
<Typography.Text
|
||||
className={s.text}
|
||||
strong
|
||||
ellipsis={
|
||||
percent === SUCCESSIVE_PROCESSING
|
||||
? {
|
||||
showTooltip: {
|
||||
opts: { content: text },
|
||||
},
|
||||
}
|
||||
: false
|
||||
}
|
||||
style={{ color: '#fff', width: '100%' }}
|
||||
>
|
||||
{text}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s['dataset-progress-format']}>{format?.(percent)}</div>
|
||||
</div>
|
||||
{status && statusDesc && status === ImportFileTaskStatus.Failed ? (
|
||||
<div className={s['dataset-progress-error']}>
|
||||
<span>{statusDesc}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
.table-preview-tips {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: var(--Light-usage-text---color-text-3, rgba(29, 28, 35, 35%));
|
||||
text-align: right;
|
||||
|
||||
/* 133.333% */
|
||||
letter-spacing: 0.12px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState, type FC } from 'react';
|
||||
|
||||
import { isNumber } from 'lodash-es';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button } from '@coze-arch/bot-semi';
|
||||
|
||||
import { Step } from '../../types';
|
||||
import { useStepStore } from '../../store/step';
|
||||
import { useInitialConfigStore } from '../../store/initial-config';
|
||||
import { useStep } from '../../hooks/use-step';
|
||||
|
||||
import styles from './index.module.less';
|
||||
export const StepFooter: FC = () => {
|
||||
const { getCallbacks } = useStep();
|
||||
const { step, enableGoToNextStep, tablePreview } = useStepStore(state => ({
|
||||
step: state.step,
|
||||
enableGoToNextStep: state.enableGoToNextStep,
|
||||
tablePreview: state.step3_tablePreview,
|
||||
}));
|
||||
const { onCancel } = useInitialConfigStore(state => ({
|
||||
onCancel: state.onCancel,
|
||||
}));
|
||||
|
||||
const stepList = Object.values(Step).filter(i => isNumber(i)) as Step[];
|
||||
const firstStep = Math.min(...stepList);
|
||||
const lastStep = Math.max(...stepList);
|
||||
const isFirstStep = step === firstStep;
|
||||
const isLastStep = step === lastStep;
|
||||
const [submitButtonLoading, setSubmitButtonLoading] = useState(false);
|
||||
|
||||
const { previewData } = tablePreview;
|
||||
const total = previewData?.total_rows || 0;
|
||||
const previewCount = previewData?.preview_rows || 10;
|
||||
|
||||
const handleClickNext = async () => {
|
||||
const { onValidate, onSubmit } = getCallbacks();
|
||||
// onValidate
|
||||
try {
|
||||
const callbackResult = onValidate?.();
|
||||
|
||||
if (callbackResult instanceof Promise) {
|
||||
setSubmitButtonLoading(true);
|
||||
}
|
||||
const res = await callbackResult;
|
||||
|
||||
// 返回 false 则直接 return
|
||||
if (typeof res === 'boolean' && res === false) {
|
||||
setSubmitButtonLoading(false);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
setSubmitButtonLoading(false);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// onSubmit
|
||||
try {
|
||||
// 判断传入的 submit 函数如果是异步,则按钮 loading
|
||||
const callbackResult = onSubmit?.();
|
||||
if (callbackResult instanceof Promise) {
|
||||
setSubmitButtonLoading(true);
|
||||
}
|
||||
await callbackResult;
|
||||
|
||||
if (isLastStep) {
|
||||
//关闭
|
||||
onCancel?.();
|
||||
} else {
|
||||
// 下一步
|
||||
useStepStore.setState(state => ({
|
||||
step: Math.min(state.step + 1, lastStep),
|
||||
}));
|
||||
}
|
||||
} finally {
|
||||
setSubmitButtonLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickPrev = () => {
|
||||
getCallbacks()?.onPrevious?.();
|
||||
if (isFirstStep) {
|
||||
// 关闭
|
||||
onCancel?.();
|
||||
} else {
|
||||
// 上一步
|
||||
useStepStore.setState(state => ({
|
||||
step: Math.max(state.step - 1, firstStep),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.footer}>
|
||||
{step === Step.Step3_TablePreview ? (
|
||||
<div className={styles['table-preview-tips']}>
|
||||
{I18n.t('db_table_0126_028', {
|
||||
TotalRows: total,
|
||||
ShowRows: previewCount,
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
{isLastStep ? null : (
|
||||
<Button type="tertiary" onClick={onCancel}>
|
||||
{I18n.t('db_table_0126_001')}
|
||||
</Button>
|
||||
)}
|
||||
{isFirstStep || isLastStep ? null : (
|
||||
<Button type="tertiary" onClick={handleClickPrev}>
|
||||
{I18n.t('db_table_0126_004')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
theme="solid"
|
||||
type="primary"
|
||||
onClick={handleClickNext}
|
||||
loading={submitButtonLoading}
|
||||
disabled={!enableGoToNextStep}
|
||||
>
|
||||
{isLastStep ? I18n.t('db_table_0126_005') : I18n.t('db_table_0126_003')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.table-preview-table-wrapper {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.table-preview-table {
|
||||
overflow: hidden;
|
||||
flex: none;
|
||||
background: var(--light-color-white-white, #FFF);
|
||||
|
||||
:global {
|
||||
.semi-table-body {
|
||||
max-height: 460px !important;
|
||||
}
|
||||
|
||||
.semi-table-colgroup .semi-table-col {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.semi-table-header {
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
|
||||
.semi-table-tbody>.semi-table-row>.semi-table-row-cell {
|
||||
min-height: 40px;
|
||||
padding: 9px 16px !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.table-preview-tips {
|
||||
margin-top: 16px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: #1C1D2399;
|
||||
text-align: left;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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 { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { type ColumnProps } from '@coze-arch/bot-semi/Table';
|
||||
import { UITable } from '@coze-arch/bot-semi';
|
||||
|
||||
import { useStepStore } from '../../store/step';
|
||||
import { useInitialConfigStore } from '../../store/initial-config';
|
||||
import outerStyles from '../../index.module.less';
|
||||
import { useStep } from '../../hooks/use-step';
|
||||
import { type AddTableResponse } from '../../datamodel';
|
||||
import { CreateType } from '../../../../types';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const TablePreview: FC = () => {
|
||||
const { onSubmit } = useStep();
|
||||
const {
|
||||
botId,
|
||||
onSave,
|
||||
// spaceId
|
||||
} = useInitialConfigStore(state => ({
|
||||
botId: state.botId,
|
||||
onSave: state.onSave,
|
||||
spaceId: state.spaceId,
|
||||
}));
|
||||
|
||||
const {
|
||||
currentState,
|
||||
setProcessing,
|
||||
// upload,
|
||||
tableStructure,
|
||||
} = useStepStore(state => ({
|
||||
currentState: state.step3_tablePreview,
|
||||
setProcessing: state.set_step4_processing,
|
||||
tableStructure: state.step2_tableStructure,
|
||||
upload: state.step1_upload,
|
||||
}));
|
||||
|
||||
const { previewData } = currentState;
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const { headers, datas } = previewData;
|
||||
// const { fileList } = upload;
|
||||
const {
|
||||
// excelValue,
|
||||
tableValue,
|
||||
} = tableStructure;
|
||||
|
||||
/**
|
||||
* headerPart: {
|
||||
* 0: 'name',
|
||||
* 1: 'age',
|
||||
* }
|
||||
*/
|
||||
const headerPart = headers.reduce<Record<number, string>>(
|
||||
(acc, cur, index) => {
|
||||
acc[index] = cur;
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
/**
|
||||
* dataPart: [
|
||||
* {
|
||||
* 0:'Nick',
|
||||
* 1: 20
|
||||
* },
|
||||
* {
|
||||
* 0:'July',
|
||||
* 1: 30
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
const dataPart = datas.map(i =>
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
i.reduce<Record<number, string>>((acc, cur, index) => {
|
||||
acc[index] = cur;
|
||||
return acc;
|
||||
}, {}),
|
||||
);
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const columns: ColumnProps[] = Object.entries(headerPart).map(
|
||||
([key, value]) => ({
|
||||
title: value,
|
||||
dataIndex: key,
|
||||
}),
|
||||
);
|
||||
|
||||
onSubmit(async () => {
|
||||
sendTeaEvent(EVENT_NAMES.create_table_click, {
|
||||
need_login: true,
|
||||
have_access: true,
|
||||
bot_id: botId,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
table_name: tableValue.name,
|
||||
database_create_type: CreateType.excel,
|
||||
});
|
||||
let res: AddTableResponse;
|
||||
try {
|
||||
// TODO:此需求暂停,后端下线,后续待开放
|
||||
// res = await DataModelApi.AddTable({
|
||||
// file: {
|
||||
// tos_uri: fileList[0].response.upload_uri,
|
||||
// sheet_id: excelValue.sheetID,
|
||||
// header_row: excelValue.headerRow,
|
||||
// start_data_row: excelValue.dataStartRow,
|
||||
// },
|
||||
// table: {
|
||||
// bot_id: botId,
|
||||
// space_id: spaceId,
|
||||
// table_name: tableValue.name,
|
||||
// table_desc: tableValue.desc,
|
||||
// table_meta: tableValue.tableMemoryList.map(i => ({
|
||||
// name: i.name,
|
||||
// desc: i.desc,
|
||||
// type: i.type,
|
||||
// must_required: i.must_required,
|
||||
// sequence: i.id as string,
|
||||
// })),
|
||||
// },
|
||||
// rw_mode: tableValue.readAndWriteMode as any as BotTableRWMode,
|
||||
// });
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseAddFromExcel,
|
||||
error: error as Error,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
if (res) {
|
||||
if (onSave) {
|
||||
await onSave({
|
||||
response: res,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
stateData: tableValue,
|
||||
});
|
||||
}
|
||||
|
||||
setProcessing({
|
||||
tableID: res.table_id as string,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(outerStyles.stepWrapper, styles['table-preview'])}
|
||||
>
|
||||
<UITable
|
||||
tableProps={{
|
||||
columns,
|
||||
dataSource: dataPart,
|
||||
pagination: false,
|
||||
className: styles['table-preview-table'],
|
||||
}}
|
||||
wrapperClassName={styles['table-preview-table-wrapper']}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
.table-structure {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
|
||||
:global {
|
||||
.semi-form-horizontal .semi-form-field {
|
||||
flex: 1;
|
||||
|
||||
.semi-select {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.semi-form-field-label-text {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 20px;
|
||||
color: #1D1C23;
|
||||
}
|
||||
|
||||
.semi-form-field-label {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.semi-form-horizontal .semi-form-field:last-child {
|
||||
margin-right: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.excel-info-form {
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.table-setting-option {
|
||||
:global {
|
||||
.semi-select-option-selected {
|
||||
.semi-select-option-icon {
|
||||
color: #4D53E8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useRef, type FC, useState, useMemo } from 'react';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import classnames from 'classnames';
|
||||
import { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Form } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
ColumnType,
|
||||
type GetTableSchemaInfoResponse,
|
||||
type FieldItemType,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { type ExcelValue } from '../../types';
|
||||
import { useStepStore } from '../../store/step';
|
||||
import { useInitialConfigStore } from '../../store/initial-config';
|
||||
import outerStyles from '../../index.module.less';
|
||||
import { useStep } from '../../hooks/use-step';
|
||||
import { type PreviewTableFileResponse } from '../../datamodel';
|
||||
import {
|
||||
DatabaseTableStructure,
|
||||
type DatabaseTableStructureRef,
|
||||
} from '../../../database-table-structure';
|
||||
import { type TableFieldsInfo, CreateType } from '../../../../types';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const TableStructure: FC = () => {
|
||||
const { onSubmit, onValidate, computingEnableGoToNextStep, onPrevious } =
|
||||
useStep();
|
||||
|
||||
const {
|
||||
botId,
|
||||
maxColumnNum,
|
||||
// spaceId
|
||||
} = useInitialConfigStore(state => ({
|
||||
botId: state.botId,
|
||||
maxColumnNum: state.maxColumnNum,
|
||||
spaceId: state.spaceId,
|
||||
}));
|
||||
|
||||
const { currentState, setCurrentState, upload, setTablePreview } =
|
||||
useStepStore(state => ({
|
||||
currentState: state.step2_tableStructure,
|
||||
setCurrentState: state.set_step2_tableStructure,
|
||||
setTablePreview: state.set_step3_tablePreview,
|
||||
upload: state.step1_upload,
|
||||
}));
|
||||
|
||||
const { excelBasicInfo, excelValue, tableValue } = currentState;
|
||||
const { fileList } = upload;
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// ref
|
||||
const excelInfoFormRef = useRef<Form<ExcelValue>>(null);
|
||||
const tableFormRef = useRef<DatabaseTableStructureRef>(null);
|
||||
|
||||
// options
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const currentSheet = excelBasicInfo.find(i => i.id === excelValue.sheetID);
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const sheetOptions = excelBasicInfo.map(i => ({
|
||||
label: i.sheet_name,
|
||||
value: i.id,
|
||||
}));
|
||||
const currentSheetTotalRow = useMemo(() => {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
let totalRow = currentSheet.total_row;
|
||||
if (totalRow < 2) {
|
||||
totalRow = 2;
|
||||
}
|
||||
if (totalRow > 50) {
|
||||
totalRow = 50;
|
||||
}
|
||||
return totalRow;
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
}, [currentSheet.total_row]);
|
||||
|
||||
const headerRowOptions = new Array(currentSheetTotalRow - 1)
|
||||
.fill(0)
|
||||
.map((_, i) => ({
|
||||
label: I18n.t('datasets_createFileModel_tab_dataStarRow_value', {
|
||||
LineNumber: i + 1,
|
||||
}),
|
||||
value: i + 1,
|
||||
}));
|
||||
const dataStartRowOptions = new Array(currentSheetTotalRow)
|
||||
.fill(0)
|
||||
.map((_, i) => ({
|
||||
label: I18n.t('datasets_createFileModel_tab_dataStarRow_value', {
|
||||
LineNumber: i + 1,
|
||||
}),
|
||||
value: i + 1,
|
||||
}))
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
.filter(i => i.value >= excelValue.headerRow + 1);
|
||||
|
||||
onSubmit(() => {
|
||||
const tableBasicValue =
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableFormRef.current.tableBasicInfoFormRef.current.formApi.getValues();
|
||||
|
||||
let res: PreviewTableFileResponse;
|
||||
try {
|
||||
// TODO:此需求暂停,后端下线,后续待开放
|
||||
// res = await DataModelApi.PreviewTableFile({
|
||||
// file: {
|
||||
// tos_uri: fileList[0].response.upload_uri,
|
||||
// sheet_id: excelValue.sheetID,
|
||||
// header_row: excelValue.headerRow,
|
||||
// start_data_row: excelValue.dataStartRow,
|
||||
// },
|
||||
// table: {
|
||||
// space_id: spaceId,
|
||||
// bot_id: botId,
|
||||
// table_name: tableBasicValue.name,
|
||||
// table_desc: tableBasicValue.desc,
|
||||
// table_meta: tableFormRef.current.tableFieldsList.map(i => ({
|
||||
// name: i.name,
|
||||
// desc: i.desc,
|
||||
// type: i.type,
|
||||
// must_required: i.must_required,
|
||||
// sequence: i.id as string,
|
||||
// })),
|
||||
// },
|
||||
// });
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseGetPreviewData,
|
||||
error: error as Error,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
if (res) {
|
||||
setTablePreview({
|
||||
previewData: res.preview_data,
|
||||
});
|
||||
setCurrentState({
|
||||
tableValue: {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableMemoryList: tableFormRef.current.tableFieldsList,
|
||||
...tableBasicValue,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableId: tableValue.tableId,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 上一步保存当前状态
|
||||
onPrevious(() => {
|
||||
const tableBasicValue =
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableFormRef.current.tableBasicInfoFormRef.current.formApi.getValues();
|
||||
setCurrentState({
|
||||
tableValue: {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableMemoryList: tableFormRef.current.tableFieldsList,
|
||||
...tableBasicValue,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableId: tableValue.tableId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
onValidate(async () => await tableFormRef.current.validate());
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(outerStyles.stepWrapper, styles['table-structure'])}
|
||||
>
|
||||
<Form<ExcelValue>
|
||||
ref={excelInfoFormRef}
|
||||
layout="horizontal"
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
initValues={{ ...excelValue }}
|
||||
className={styles['excel-info-form']}
|
||||
onValueChange={async (values, changedValue) => {
|
||||
const changedKeys = Object.keys(changedValue);
|
||||
|
||||
const reloadTableValue = async (
|
||||
params: {
|
||||
updateTableName?: boolean;
|
||||
} = {},
|
||||
) => {
|
||||
const { updateTableName = false } = params;
|
||||
const basicInfo =
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableFormRef.current.tableBasicInfoFormRef.current.formApi.getValues();
|
||||
setLoading(true);
|
||||
|
||||
let res: GetTableSchemaInfoResponse;
|
||||
try {
|
||||
res = await MemoryApi.GetTableSchemaInfo({
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tos_uri: fileList[0].response.upload_uri,
|
||||
doc_table_info: {
|
||||
sheet_id: values.sheetID,
|
||||
header_line_idx: values.headerRow - 1,
|
||||
start_line_idx: values.dataStartRow - 1,
|
||||
} as any,
|
||||
});
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseGetExcelInfo,
|
||||
error: error as Error,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
if (res) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const newTableMemoryList: TableFieldsInfo = res.table_meta.map(
|
||||
i => ({
|
||||
name: i.column_name,
|
||||
nanoid: nanoid(),
|
||||
desc: '',
|
||||
type:
|
||||
i.column_type === ColumnType.Unknown
|
||||
? undefined
|
||||
: (i.column_type as any as FieldItemType),
|
||||
must_required: false,
|
||||
disableMustRequired: i.contains_empty_value,
|
||||
}),
|
||||
);
|
||||
setCurrentState({
|
||||
excelValue: { ...values },
|
||||
tableValue: {
|
||||
name: updateTableName
|
||||
? // @ts-expect-error -- linter-disable-autofix
|
||||
excelBasicInfo.find(i => i.id === values.sheetID)
|
||||
?.sheet_name || basicInfo.name
|
||||
: basicInfo.name,
|
||||
desc: basicInfo.desc,
|
||||
readAndWriteMode: basicInfo.readAndWriteMode,
|
||||
tableId: '',
|
||||
tableMemoryList: newTableMemoryList,
|
||||
},
|
||||
});
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableFormRef.current.setTableFieldsList(newTableMemoryList);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (changedKeys.length === 1 && changedKeys.includes('sheetID')) {
|
||||
// FIXME: 此处 semi 有 bug,始终是 override 更新,所以需要也添加上 sheetID 属性
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
excelInfoFormRef.current.formApi.setValues({
|
||||
sheetID: changedValue.sheetID,
|
||||
headerRow: 1,
|
||||
dataStartRow: 2,
|
||||
});
|
||||
}
|
||||
|
||||
// 切换 sheet
|
||||
if (
|
||||
changedKeys.length === 3 &&
|
||||
changedKeys.includes('headerRow') &&
|
||||
changedKeys.includes('dataStartRow') &&
|
||||
changedKeys.includes('sheetID')
|
||||
) {
|
||||
await reloadTableValue({ updateTableName: true });
|
||||
}
|
||||
|
||||
// 仅更新 headerRow
|
||||
if (changedKeys.length === 1 && changedKeys.includes('headerRow')) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
if (changedValue.headerRow >= values.dataStartRow) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
excelInfoFormRef.current.formApi.setValue(
|
||||
'dataStartRow',
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
changedValue.headerRow + 1,
|
||||
);
|
||||
} else {
|
||||
await reloadTableValue();
|
||||
}
|
||||
}
|
||||
|
||||
// 仅更新 dataStartRow
|
||||
if (
|
||||
changedKeys.length === 1 &&
|
||||
changedKeys.includes('dataStartRow')
|
||||
) {
|
||||
await reloadTableValue();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Form.Select
|
||||
field="sheetID"
|
||||
label={I18n.t('datasets_createFileModel_tab_DataSheet')}
|
||||
dropdownClassName={styles['table-setting-option']}
|
||||
optionList={sheetOptions}
|
||||
/>
|
||||
<Form.Select
|
||||
field="headerRow"
|
||||
label={I18n.t('datasets_createFileModel_tab_header')}
|
||||
dropdownClassName={styles['table-setting-option']}
|
||||
optionList={headerRowOptions}
|
||||
/>
|
||||
<Form.Select
|
||||
field="dataStartRow"
|
||||
label={I18n.t('datasets_createFileModel_tab_dataStarRow')}
|
||||
dropdownClassName={styles['table-setting-option']}
|
||||
optionList={dataStartRowOptions}
|
||||
/>
|
||||
</Form>
|
||||
<DatabaseTableStructure
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
data={tableValue}
|
||||
botId={botId}
|
||||
forceEdit
|
||||
ref={tableFormRef}
|
||||
useComputingEnableGoToNextStep={(tableFieldsList: TableFieldsInfo) => {
|
||||
computingEnableGoToNextStep(() => {
|
||||
const currentLength = tableFieldsList.length;
|
||||
return currentLength >= 1 && currentLength <= maxColumnNum;
|
||||
});
|
||||
}}
|
||||
loading={loading}
|
||||
enableAdd={false}
|
||||
readAndWriteModeOptions="excel"
|
||||
maxColumnNum={maxColumnNum}
|
||||
createType={CreateType.excel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
.file-name-wrapper {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.file-name-label {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: var(--Light-usage-text---color-text-0, #1D1C23);
|
||||
}
|
||||
|
||||
|
||||
.file-list-table {
|
||||
:global {
|
||||
.semi-table-container {
|
||||
padding: 0 20px;
|
||||
border: 1px solid var(--Light-usage-border---color-border-1, rgba(29, 28, 35, 12%));
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
// 此处不加 !important 优先级不够,会被 semi 的样式覆盖掉
|
||||
.semi-table-row:hover {
|
||||
cursor: default !important;
|
||||
|
||||
>.semi-table-row-cell {
|
||||
background: none !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 此处不加 !important 优先级不够,会被 semi 的样式覆盖掉
|
||||
.semi-table-row {
|
||||
>.semi-table-row-cell {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.semi-table-header {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.semi-table-body {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.semi-table-row-cell {
|
||||
border: none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.upload {
|
||||
:global {
|
||||
.semi-upload-drag-area-sub-text {
|
||||
color: rgba(136, 141, 146, 100%)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
/*
|
||||
* 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 { useCallback, type FC } from 'react';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type UploadProps, type FileItem } from '@coze-arch/bot-semi/Upload';
|
||||
import {
|
||||
UIIconButton,
|
||||
UITable,
|
||||
Popover,
|
||||
Progress,
|
||||
Upload as SemiUpload,
|
||||
Toast,
|
||||
Tooltip,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import {
|
||||
IconDeleteOutline,
|
||||
IconUploadFileFail,
|
||||
IconUploadFileSuccess,
|
||||
} from '@coze-arch/bot-icons';
|
||||
import {
|
||||
ColumnType,
|
||||
type GetTableSchemaInfoResponse,
|
||||
BotTableRWMode,
|
||||
type FieldItemType,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
import {
|
||||
FileBizType,
|
||||
type UploadFileResponse,
|
||||
type UploadFileData,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi, MemoryApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { type SheetItem } from '../../types';
|
||||
import { useStepStore } from '../../store/step';
|
||||
import outerStyles from '../../index.module.less';
|
||||
import { useStep } from '../../hooks/use-step';
|
||||
import { getFileIcon } from '../../helpers/get-file-icon';
|
||||
import { getFileExtension } from '../../helpers/get-file-extension';
|
||||
import { getBase64 } from '../../helpers/get-base64';
|
||||
import { ACCEPT_FILE_TYPES, ACCEPT_FILE_MAX_SIZE } from '../../const';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const UploadStatusComp = (props: {
|
||||
record: FileItem;
|
||||
onRetry: (record: FileItem, index: number) => void;
|
||||
index: number;
|
||||
}) => {
|
||||
const { record, onRetry, index } = props;
|
||||
const { status } = record;
|
||||
if (status === 'uploading' || status === 'validating' || status === 'wait') {
|
||||
return (
|
||||
<span className={styles['upload-status-wrap']}>
|
||||
<span>{I18n.t('datasets_unit_upload_state')}</span>
|
||||
<Progress percent={record.percent} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (status === 'success') {
|
||||
return (
|
||||
<span className={styles['upload-status-wrap']}>
|
||||
<IconUploadFileSuccess />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (status === 'validateFail') {
|
||||
return (
|
||||
<span className={styles['upload-status-wrap']}>
|
||||
<IconUploadFileFail />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (status === 'uploadFail') {
|
||||
return (
|
||||
<span
|
||||
className={`${styles['upload-status-wrap']} ${styles.retry}`}
|
||||
onClick={() => {
|
||||
onRetry && onRetry(record, index);
|
||||
}}
|
||||
>
|
||||
<Popover
|
||||
className={styles['fail-popover']}
|
||||
content={record.statusDescript}
|
||||
visible
|
||||
trigger="custom"
|
||||
>
|
||||
<IconUploadFileFail />
|
||||
</Popover>
|
||||
<span className={styles['retry-text']}>
|
||||
{I18n.t('datasets_unit_update_retry')}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const transformUnitList = ({
|
||||
fileList,
|
||||
data,
|
||||
fileInstance,
|
||||
index,
|
||||
}: {
|
||||
fileList: FileItem[];
|
||||
data: UploadFileData | undefined;
|
||||
fileInstance: File;
|
||||
index: number;
|
||||
}): FileItem[] => {
|
||||
if (!data) {
|
||||
return fileList;
|
||||
}
|
||||
const filteredList = fileList.map((file, i) => {
|
||||
if (index === i) {
|
||||
return {
|
||||
...file,
|
||||
uri: data.upload_uri || '',
|
||||
status: 'success' as const,
|
||||
percent: 100,
|
||||
fileInstance,
|
||||
};
|
||||
}
|
||||
return file;
|
||||
});
|
||||
return filteredList;
|
||||
};
|
||||
|
||||
export const Upload: FC = () => {
|
||||
const { onSubmit, computingEnableGoToNextStep } = useStep();
|
||||
const { currentState, setCurrentState, tableStructure, setTableStructure } =
|
||||
useStepStore(state => ({
|
||||
currentState: state.step1_upload,
|
||||
setCurrentState: state.set_step1_upload,
|
||||
tableStructure: state.step2_tableStructure,
|
||||
setTableStructure: state.set_step2_tableStructure,
|
||||
tablePreview: state.step3_tablePreview,
|
||||
}));
|
||||
|
||||
const { fileList = [] } = currentState;
|
||||
|
||||
const customRequest: UploadProps['customRequest'] = async options => {
|
||||
const { onSuccess, onError, onProgress, file } = options;
|
||||
|
||||
if (typeof file === 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 业务
|
||||
const { name, fileInstance } = file;
|
||||
|
||||
if (fileInstance) {
|
||||
const extension = getFileExtension(name);
|
||||
|
||||
const base64 = await getBase64(fileInstance);
|
||||
|
||||
let result: UploadFileResponse;
|
||||
try {
|
||||
result = await DeveloperApi.UploadFile(
|
||||
{
|
||||
file_head: {
|
||||
file_type: extension,
|
||||
biz_type: FileBizType.BIZ_BOT_DATASET,
|
||||
},
|
||||
data: base64,
|
||||
},
|
||||
{
|
||||
onUploadProgress: e => {
|
||||
onProgress({
|
||||
total: e.total ?? fileInstance.size,
|
||||
loaded: e.loaded,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseUploadExcelFile,
|
||||
error: error as Error,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
if (result) {
|
||||
onSuccess(result.data);
|
||||
}
|
||||
} else {
|
||||
throw new Error('Failed to upload database');
|
||||
}
|
||||
} catch (e) {
|
||||
onError({
|
||||
status: 0,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onRetry = async (record: FileItem, 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,
|
||||
});
|
||||
|
||||
setCurrentState({
|
||||
fileList: transformUnitList({
|
||||
fileList,
|
||||
data: result?.data,
|
||||
fileInstance,
|
||||
index,
|
||||
}),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseUploadExcelFile,
|
||||
error: error as Error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteFile = (index: number) => {
|
||||
setCurrentState({
|
||||
fileList: fileList.filter((f, i) => index !== i),
|
||||
});
|
||||
// reset 配置
|
||||
setTableStructure({
|
||||
excelBasicInfo: undefined,
|
||||
excelValue: undefined,
|
||||
tableValue: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: I18n.t('db_table_0126_018'),
|
||||
dataIndex: 'name',
|
||||
width: 643,
|
||||
render: (_, record) => {
|
||||
const { name } = record;
|
||||
const extension = getFileExtension(name);
|
||||
return (
|
||||
<div className={styles['file-name-wrapper']}>
|
||||
{getFileIcon(extension)}
|
||||
<span className={styles['file-name-label']}>{name}</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: I18n.t('db_table_0126_019'),
|
||||
dataIndex: 'status',
|
||||
render: (_, record, index) => (
|
||||
<UploadStatusComp onRetry={onRetry} record={record} index={index} />
|
||||
),
|
||||
width: 153,
|
||||
},
|
||||
{
|
||||
title: I18n.t('db_table_0126_020'),
|
||||
dataIndex: 'size',
|
||||
width: 100,
|
||||
render: (_, record) => <span>{record.size}</span>,
|
||||
},
|
||||
{
|
||||
title: I18n.t('db_table_0126_021'),
|
||||
dataIndex: 'action',
|
||||
width: 72,
|
||||
render: (_, record: FileItem, index) => {
|
||||
const disabled =
|
||||
record.status === 'uploading' ||
|
||||
record.status === 'validating' ||
|
||||
record.status === 'wait';
|
||||
return (
|
||||
<div
|
||||
className={styles['ui-action-content']}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Tooltip spacing={12} content={I18n.t('Delete')} position="top">
|
||||
<UIIconButton
|
||||
disabled={disabled}
|
||||
icon={<IconDeleteOutline className={styles.icon} />}
|
||||
style={{
|
||||
color: disabled
|
||||
? 'rgba(136, 138, 142, 0.5)'
|
||||
: 'rgba(136, 138, 142, 1)',
|
||||
}}
|
||||
onClick={() => handleDeleteFile(index)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
onSubmit(async () => {
|
||||
if (!tableStructure.excelBasicInfo) {
|
||||
let res: GetTableSchemaInfoResponse;
|
||||
try {
|
||||
res = await MemoryApi.GetTableSchemaInfo({
|
||||
tos_uri: fileList[0].response.upload_uri,
|
||||
});
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseGetExcelInfo,
|
||||
error: error as Error,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
if (res) {
|
||||
const { sheet_list, table_meta } = res;
|
||||
setTableStructure({
|
||||
excelBasicInfo: sheet_list as SheetItem[],
|
||||
excelValue: {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
sheetID: sheet_list[0]?.id as number,
|
||||
headerRow: 1,
|
||||
dataStartRow: 2,
|
||||
},
|
||||
tableValue: {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
name: sheet_list[0].sheet_name,
|
||||
desc: '',
|
||||
tableId: '',
|
||||
readAndWriteMode: BotTableRWMode.ReadOnly,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableMemoryList: table_meta.map(i => ({
|
||||
name: i.column_name,
|
||||
nanoid: nanoid(),
|
||||
desc: '',
|
||||
type:
|
||||
i.column_type === ColumnType.Unknown
|
||||
? undefined
|
||||
: (i.column_type as any as FieldItemType),
|
||||
must_required: false,
|
||||
id: i.sequence,
|
||||
disableMustRequired: i.contains_empty_value,
|
||||
})),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
computingEnableGoToNextStep(
|
||||
useCallback(
|
||||
() => fileList.length > 0 && fileList.some(i => i.status === 'success'),
|
||||
[fileList],
|
||||
),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={outerStyles.stepWrapper}>
|
||||
<SemiUpload
|
||||
style={{
|
||||
height: '100%',
|
||||
/**
|
||||
* NOTE: 此处采取 css 隐藏是为了保持 upload 过程,否则会取消上传
|
||||
*/
|
||||
...(fileList.length > 0
|
||||
? {
|
||||
display: 'none',
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
onAcceptInvalid={() => {
|
||||
Toast.warning({
|
||||
showClose: false,
|
||||
content: I18n.t('db_table_0126_032'),
|
||||
});
|
||||
}}
|
||||
beforeUpload={fileInfo => {
|
||||
// 不通过 maxSize 属性来限制的原因是
|
||||
// 只有 beforeUpload 钩子能改 validateMessage
|
||||
const res = {
|
||||
fileInstance: fileInfo.file.fileInstance,
|
||||
status: fileInfo.file.status,
|
||||
validateMessage: fileInfo.file.validateMessage,
|
||||
shouldUpload: true,
|
||||
autoRemove: false,
|
||||
};
|
||||
const { fileInstance } = fileInfo.file;
|
||||
if (!fileInstance) {
|
||||
return {
|
||||
...res,
|
||||
status: 'uploadFail',
|
||||
shouldUpload: false,
|
||||
};
|
||||
}
|
||||
if (fileInstance.size > ACCEPT_FILE_MAX_SIZE) {
|
||||
Toast.warning({
|
||||
showClose: false,
|
||||
content: I18n.t('file_too_large', {
|
||||
max_size: '20MB',
|
||||
}),
|
||||
});
|
||||
return {
|
||||
...res,
|
||||
shouldUpload: false,
|
||||
status: 'validateFail',
|
||||
validateMessage: I18n.t('file_too_large', {
|
||||
max_size: '20MB',
|
||||
}),
|
||||
};
|
||||
}
|
||||
return res;
|
||||
}}
|
||||
limit={1}
|
||||
draggable={true}
|
||||
showUploadList={false}
|
||||
accept={ACCEPT_FILE_TYPES}
|
||||
customRequest={customRequest}
|
||||
dragMainText={I18n.t('db_table_0126_016')}
|
||||
dragSubText={I18n.t('db_table_0126_017')}
|
||||
onChange={({ fileList: files }) => {
|
||||
// 存在校验通过的才上传
|
||||
if (files.some(f => f.shouldUpload)) {
|
||||
setCurrentState({
|
||||
fileList: files,
|
||||
});
|
||||
}
|
||||
}}
|
||||
className={styles.upload}
|
||||
/>
|
||||
{fileList.length > 0 ? (
|
||||
<UITable
|
||||
tableProps={{
|
||||
dataSource: fileList,
|
||||
columns,
|
||||
className: styles['file-list-table'],
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// 20MB 限制
|
||||
export const ACCEPT_FILE_MAX_SIZE = 20 * 1024 * 1024;
|
||||
export const ACCEPT_FILE_TYPES = ['.xlsx', '.xls', '.csv'].join(',');
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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 */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import * as base from './namespaces/base';
|
||||
import * as table_base from './namespaces/table_base';
|
||||
import * as table_import from './namespaces/table_import';
|
||||
export { base, table_base, table_import };
|
||||
export * from './namespaces/base';
|
||||
export * from './namespaces/table_base';
|
||||
export * from './namespaces/table_import';
|
||||
|
||||
export type Int64 = string | number;
|
||||
|
||||
export default class DatamodelService<T> {
|
||||
private request: any = () => {
|
||||
throw new Error('DatamodelService.request is undefined');
|
||||
};
|
||||
private baseURL: string | ((path: string) => string) = '';
|
||||
|
||||
constructor(options?: {
|
||||
baseURL?: string | ((path: string) => string);
|
||||
request?<R>(
|
||||
params: {
|
||||
url: string;
|
||||
method: 'GET' | 'DELETE' | 'POST' | 'PUT' | 'PATCH';
|
||||
data?: any;
|
||||
params?: any;
|
||||
headers?: any;
|
||||
},
|
||||
options?: T,
|
||||
): Promise<R>;
|
||||
}) {
|
||||
this.request = options?.request || this.request;
|
||||
this.baseURL = options?.baseURL || '';
|
||||
}
|
||||
|
||||
private genBaseURL(path: string) {
|
||||
return typeof this.baseURL === 'string'
|
||||
? this.baseURL + path
|
||||
: this.baseURL(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/datamodel/tablefile/preview
|
||||
*
|
||||
* [jump to BAM]()
|
||||
*
|
||||
* 【Table Import】导入文件数据预检查
|
||||
*/
|
||||
PreviewTableFile(
|
||||
req: table_import.PreviewTableFileRequest,
|
||||
options?: T,
|
||||
): Promise<table_import.PreviewTableFileResponse> {
|
||||
const url = this.genBaseURL('/api/datamodel/tablefile/preview');
|
||||
const method = 'POST';
|
||||
const _req = req || {};
|
||||
const data = {
|
||||
table: _req['table'],
|
||||
file: _req['file'],
|
||||
Base: _req['Base'],
|
||||
};
|
||||
return this.request({ url, method, data }, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/datamodel/tablefiletask/query
|
||||
*
|
||||
* [jump to BAM]()
|
||||
*
|
||||
* 【Table Import】导入文件数据任务信息查询
|
||||
*/
|
||||
QueryTableFileTaskStatus(
|
||||
req: table_import.QueryTableFileTaskStatusRequest,
|
||||
options?: T,
|
||||
): Promise<table_import.QueryTableFileTaskStatusResponse> {
|
||||
const url = this.genBaseURL('/api/datamodel/tablefiletask/query');
|
||||
const method = 'POST';
|
||||
const _req = req || {};
|
||||
const data = {
|
||||
table_id: _req['table_id'],
|
||||
bot_id: _req['bot_id'],
|
||||
task_id: _req['task_id'],
|
||||
Base: _req['Base'],
|
||||
};
|
||||
return this.request({ url, method, data }, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/datamodel/table/add
|
||||
*
|
||||
* [jump to BAM]()
|
||||
*
|
||||
* 【Table Import】导入文件添加table
|
||||
*/
|
||||
AddTable(
|
||||
req: table_import.AddTableRequest,
|
||||
options?: T,
|
||||
): Promise<table_import.AddTableResponse> {
|
||||
const url = this.genBaseURL('/api/datamodel/table/add');
|
||||
const method = 'POST';
|
||||
const _req = req || {};
|
||||
const data = {
|
||||
table: _req['table'],
|
||||
file: _req['file'],
|
||||
rw_mode: _req['rw_mode'],
|
||||
Base: _req['Base'],
|
||||
};
|
||||
return this.request({ url, method, data }, options);
|
||||
}
|
||||
}
|
||||
/* eslint-enable */
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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 */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
|
||||
export type Int64 = string | number;
|
||||
|
||||
export interface Base {
|
||||
LogID?: string;
|
||||
Caller?: string;
|
||||
Addr?: string;
|
||||
Client?: string;
|
||||
TrafficEnv?: TrafficEnv;
|
||||
Extra?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface TrafficEnv {
|
||||
Open?: boolean;
|
||||
Env?: string;
|
||||
}
|
||||
/* eslint-enable */
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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 */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
|
||||
export type Int64 = string | number;
|
||||
|
||||
export enum BotTableRWMode {
|
||||
/** 单用户模式 */
|
||||
LimitedReadWrite = 1,
|
||||
/** 只读模式 */
|
||||
ReadOnly = 2,
|
||||
/** 多用户模式 */
|
||||
UnlimitedReadWrite = 3,
|
||||
/** Max 边界值 */
|
||||
RWModeMax = 4,
|
||||
}
|
||||
|
||||
export enum BotTableStatus {
|
||||
/** 初始化(不可用) */
|
||||
Init = 0,
|
||||
/** 已上线 */
|
||||
Online = 1,
|
||||
/** 删除 */
|
||||
Delete = 2,
|
||||
/** 草稿态(未 publish) */
|
||||
Draft = 3,
|
||||
}
|
||||
|
||||
export enum FieldItemType {
|
||||
/** 文本 */
|
||||
Text = 1,
|
||||
/** 数字 */
|
||||
Number = 2,
|
||||
/** 时间 */
|
||||
Date = 3,
|
||||
/** float */
|
||||
Float = 4,
|
||||
/** bool */
|
||||
Boolean = 5,
|
||||
}
|
||||
|
||||
/** Table model相关常量,结构体定义 */
|
||||
export enum FieldType {
|
||||
/** 文本 */
|
||||
Text = 1,
|
||||
/** 数字 */
|
||||
Number = 2,
|
||||
/** 时间 */
|
||||
Date = 3,
|
||||
/** float */
|
||||
Float = 4,
|
||||
/** bool */
|
||||
Boolean = 5,
|
||||
}
|
||||
|
||||
export enum ImportFileTaskStatus {
|
||||
/** 任务初始化 */
|
||||
Init = 1,
|
||||
/** 任务处理中 */
|
||||
Enqueue = 2,
|
||||
/** 任务成功 */
|
||||
Succeed = 3,
|
||||
/** 任务失败 */
|
||||
Failed = 4,
|
||||
}
|
||||
|
||||
export enum Language {
|
||||
/** 中文 */
|
||||
Chinese = 1,
|
||||
/** 英文 */
|
||||
English = 2,
|
||||
}
|
||||
|
||||
export enum TableType {
|
||||
/** 草稿 */
|
||||
DraftTable = 1,
|
||||
/** 线上 */
|
||||
OnlineTable = 2,
|
||||
}
|
||||
|
||||
export interface FieldItem {
|
||||
/** 字段名称,用户自定义,可能为中文 */
|
||||
name: string;
|
||||
desc?: string;
|
||||
type: FieldItemType;
|
||||
must_required?: boolean;
|
||||
/** 字段Id,服务端生成,全局唯一(新增为0) */
|
||||
id?: Int64;
|
||||
/** 字段名称语言类型 */
|
||||
lang?: Language;
|
||||
/** 物理字段名,服务端生成,单个table下唯一 */
|
||||
physics_name?: string;
|
||||
/** 是否主键 */
|
||||
primary_key?: boolean;
|
||||
/** 字段可见性,1:用户自定义;2:业务定义,对用户可见;3:业务定义,对用户隐藏 */
|
||||
visibility?: number;
|
||||
/** 在excel文档中使用,映射到excel中对应的列 */
|
||||
sequence?: string;
|
||||
/** 业务自定义扩展field元数据 */
|
||||
map_ext_meta?: Record<string, string>;
|
||||
}
|
||||
/* eslint-enable */
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import * as table_base from './table_base';
|
||||
import * as base from './base';
|
||||
|
||||
export type Int64 = string | number;
|
||||
|
||||
export interface AddTableRequest {
|
||||
/** table schema */
|
||||
table: ImportTableInfo;
|
||||
/** file source */
|
||||
file: FileInfo;
|
||||
rw_mode: table_base.BotTableRWMode;
|
||||
Base?: base.Base;
|
||||
}
|
||||
|
||||
export interface AddTableResponse {
|
||||
/** 相关id. bot_id */
|
||||
bot_id: string;
|
||||
/** table_id */
|
||||
table_id: string;
|
||||
/** 表名 */
|
||||
table_name: string;
|
||||
/** 上传 TableFile 的 task id,用于后期查询使用.
|
||||
DataModelService 使用 GID 保证 task_id 全局唯一,后续查询时只需要 task_id.
|
||||
DataModel 服务会记录 table 的 lastTaskID, 查询时可以通过 table_id 查到唯一的
|
||||
task_id */
|
||||
task_id: Int64;
|
||||
code: number;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
export interface FileInfo {
|
||||
/** tos uri */
|
||||
tos_uri: string;
|
||||
/** Excel 行号 */
|
||||
header_row: Int64;
|
||||
/** Excel 数据开始行 */
|
||||
start_data_row: Int64;
|
||||
/** Excel sheet id, 0 for default */
|
||||
sheet_id?: number;
|
||||
}
|
||||
|
||||
export interface ImportTableInfo {
|
||||
/** table 所属的 bot_id */
|
||||
bot_id: string;
|
||||
/** 表名 */
|
||||
table_name: string;
|
||||
/** 表描述 */
|
||||
table_desc?: string;
|
||||
/** 字段信息 */
|
||||
table_meta: Array<table_base.FieldItem>;
|
||||
/** 空间ID */
|
||||
space_id: string;
|
||||
}
|
||||
|
||||
export interface PreviewTableFileRequest {
|
||||
table: ImportTableInfo;
|
||||
file: FileInfo;
|
||||
Base?: base.Base;
|
||||
}
|
||||
|
||||
export interface PreviewTableFileResponse {
|
||||
preview_data?: SheetInfo;
|
||||
code: number;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
export interface QueryTableFileTaskStatusRequest {
|
||||
table_id: string;
|
||||
bot_id?: string;
|
||||
task_id?: Int64;
|
||||
Base?: base.Base;
|
||||
}
|
||||
|
||||
export interface QueryTableFileTaskStatusResponse {
|
||||
table_id: string;
|
||||
/** filled by server. callers may use it to do some additional business */
|
||||
table_name: string;
|
||||
/** filled by server. callers may use it to do some additional business */
|
||||
bot_id: string;
|
||||
/** filled by server. callers may use it to do some additional business */
|
||||
task_id: Int64;
|
||||
/** 10 for 10%, 100 for 100% */
|
||||
progress: Int64;
|
||||
status: table_base.ImportFileTaskStatus;
|
||||
/** in json format */
|
||||
summary: string;
|
||||
/** tos url */
|
||||
error_detail_url: string;
|
||||
code: number;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
export interface SheetInfo {
|
||||
headers?: Array<string>;
|
||||
datas?: Array<Array<string>>;
|
||||
total_rows?: Int64;
|
||||
preview_rows?: Int64;
|
||||
}
|
||||
/* eslint-enable */
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 Callback } from '../types';
|
||||
|
||||
class EventEmitter {
|
||||
private eventMap = new Map<string, Callback>();
|
||||
|
||||
on(event: string, callback: Callback): void {
|
||||
this.eventMap.set(event, callback);
|
||||
}
|
||||
|
||||
off(event: string): void {
|
||||
this.eventMap.delete(event);
|
||||
}
|
||||
|
||||
getEventCallback(event: string): Callback | undefined {
|
||||
return this.eventMap.get(event);
|
||||
}
|
||||
|
||||
emit(event: string): Promise<void> | void {
|
||||
return this.getEventCallback(event)?.() as any;
|
||||
}
|
||||
}
|
||||
|
||||
export const eventEmitter = new EventEmitter();
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export function getBase64(file: Blob): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = event => {
|
||||
const result = event.target?.result;
|
||||
|
||||
if (!result || typeof result !== 'string') {
|
||||
reject(new Error('file read fail'));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(result.replace(/^.*?,/, ''));
|
||||
};
|
||||
fileReader.readAsDataURL(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.
|
||||
*/
|
||||
|
||||
export const getFileExtension = (name: string) => {
|
||||
const index = name.lastIndexOf('.');
|
||||
return name.slice(index + 1);
|
||||
};
|
||||
@@ -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 { Icon } from '@coze-arch/bot-semi';
|
||||
|
||||
import { ReactComponent as ExcelSVG } from '../../../assets/icon_wiki-excel_colorful.svg';
|
||||
import { ReactComponent as CSVSVG } from '../../../assets/icon_wiki-csv_colorful.svg';
|
||||
|
||||
export const getFileIcon = (extension: string) => {
|
||||
if (extension === 'xlsx' || extension === 'xltx') {
|
||||
return <Icon svg={<ExcelSVG />} />;
|
||||
}
|
||||
if (extension === 'csv') {
|
||||
return <Icon svg={<CSVSVG />} />;
|
||||
}
|
||||
// TODO
|
||||
return <Icon svg={<ExcelSVG />} />;
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { type Callback } from '../types';
|
||||
import { useStepStore } from '../store/step';
|
||||
import { eventEmitter } from '../helpers/event-emitter';
|
||||
|
||||
const generateEventCallback =
|
||||
(eventName: 'validate' | 'next' | 'prev') =>
|
||||
(callback: Callback): void => {
|
||||
const step = useStepStore(state => state.step);
|
||||
useEffect(() => {
|
||||
const key = `${eventName}-${step}`;
|
||||
eventEmitter.on(key, callback);
|
||||
|
||||
return () => {
|
||||
eventEmitter.off(key);
|
||||
};
|
||||
}, [callback, step]);
|
||||
};
|
||||
|
||||
export const useStep = () => {
|
||||
const step = useStepStore(state => state.step);
|
||||
const enableGoToNextStep = useStepStore(state => state.enableGoToNextStep);
|
||||
const set_enableGoToNextStep = useStepStore(
|
||||
state => state.set_enableGoToNextStep,
|
||||
);
|
||||
|
||||
const computingEnableGoToNextStep = (compute: () => boolean) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks -- linter-disable-autofix
|
||||
useEffect(() => {
|
||||
const res = compute();
|
||||
if (res !== enableGoToNextStep) {
|
||||
set_enableGoToNextStep(res);
|
||||
}
|
||||
}, [compute, enableGoToNextStep]);
|
||||
};
|
||||
|
||||
return {
|
||||
computingEnableGoToNextStep,
|
||||
onValidate: generateEventCallback('validate'),
|
||||
onSubmit: generateEventCallback('next'),
|
||||
onPrevious: generateEventCallback('prev'),
|
||||
getCallbacks: () => ({
|
||||
onValidate: eventEmitter.getEventCallback(`validate-${step}`),
|
||||
onSubmit: eventEmitter.getEventCallback(`next-${step}`),
|
||||
onPrevious: eventEmitter.getEventCallback(`prev-${step}`),
|
||||
}),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
|
||||
import { useLocalStorageState } from 'ahooks';
|
||||
import { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
|
||||
import {
|
||||
ImportFileTaskStatus,
|
||||
type QueryTableFileTaskStatusResponse,
|
||||
} from '../datamodel';
|
||||
|
||||
export interface ProgressInfo {
|
||||
progress: number;
|
||||
status: ImportFileTaskStatus;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export const useUploadProgress = (params: {
|
||||
tableID: string;
|
||||
botID: string;
|
||||
}): ProgressInfo | undefined => {
|
||||
const { tableID, botID } = params;
|
||||
const [pollingInfo, setPollingInfo] = useState<ProgressInfo>();
|
||||
|
||||
const importResultRef = useRef<number>();
|
||||
|
||||
const [
|
||||
mapOfShouldQueryDatabaseProcessStatus,
|
||||
setMapOfShouldQueryDatabaseProcessStatus,
|
||||
] = useLocalStorageState<string | undefined>(
|
||||
'map-of-should-query-database-process-status',
|
||||
{
|
||||
defaultValue: '',
|
||||
},
|
||||
);
|
||||
|
||||
const writeToLocalStorage = () => {
|
||||
try {
|
||||
const lsMap = JSON.parse(mapOfShouldQueryDatabaseProcessStatus || '{}');
|
||||
const value = lsMap?.[botID] || [];
|
||||
value.push(tableID);
|
||||
lsMap[botID] = value;
|
||||
setMapOfShouldQueryDatabaseProcessStatus(JSON.stringify(lsMap));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const readFromLocalStorage = (): Record<string, string[]> => {
|
||||
let lsMap = {};
|
||||
try {
|
||||
lsMap = JSON.parse(mapOfShouldQueryDatabaseProcessStatus || '{}');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return lsMap;
|
||||
};
|
||||
|
||||
const startReceiveTimeCheck = () => {
|
||||
try {
|
||||
let res: QueryTableFileTaskStatusResponse;
|
||||
try {
|
||||
// TODO:此需求暂停,后端下线,后续待开放
|
||||
// res = await DataModelApi.QueryTableFileTaskStatus({
|
||||
// table_id: tableID,
|
||||
// bot_id: botID,
|
||||
// });
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseGetTaskInfo,
|
||||
error: error as Error,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 不在Processing则停止轮询
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
if (res?.status !== ImportFileTaskStatus.Enqueue) {
|
||||
clearInterval(importResultRef.current);
|
||||
importResultRef.current = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功后---写入localStorage
|
||||
* 没有任务---写入localStorage
|
||||
*/
|
||||
if (
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
!res?.status ||
|
||||
res?.status === (0 as any) ||
|
||||
res?.status === ImportFileTaskStatus.Succeed
|
||||
) {
|
||||
writeToLocalStorage();
|
||||
}
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
if (res) {
|
||||
setPollingInfo({
|
||||
progress: Number(res.progress),
|
||||
status: res.status,
|
||||
errorMessage: res.summary,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
clearInterval(importResultRef.current);
|
||||
importResultRef.current = undefined;
|
||||
setPollingInfo({
|
||||
progress: 0,
|
||||
status: ImportFileTaskStatus.Failed,
|
||||
errorMessage: (error as Error).message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 读取localStorage,减少不必要的请求次数
|
||||
const lsMap = readFromLocalStorage();
|
||||
const inLocaleStorage = (lsMap[botID] || []).includes(tableID);
|
||||
|
||||
if (tableID && !inLocaleStorage) {
|
||||
importResultRef.current = setInterval(() => {
|
||||
startReceiveTimeCheck();
|
||||
}, 1000) as unknown as number;
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearInterval(importResultRef.current);
|
||||
};
|
||||
}, [tableID]);
|
||||
|
||||
return pollingInfo;
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
.stepWrapper {
|
||||
width: 100%;
|
||||
height: 500px
|
||||
}
|
||||
|
||||
.create-from-excel-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
.steps {
|
||||
width: 700px;
|
||||
margin-bottom: 40px
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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, type FC } from 'react';
|
||||
|
||||
import { type DatabaseInfo } from '@coze-studio/bot-detail-store';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Steps } from '@coze-arch/bot-semi';
|
||||
|
||||
import { Step } from './types';
|
||||
import { useStepStore } from './store/step';
|
||||
import { useInitialConfigStore } from './store/initial-config';
|
||||
import { Upload } from './components/upload';
|
||||
import { TableStructure } from './components/table-structure';
|
||||
import { TablePreview } from './components/table-preview';
|
||||
import { Processing } from './components/processing';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const map = {
|
||||
[Step.Step1_Upload]: Upload,
|
||||
[Step.Step2_TableStructure]: TableStructure,
|
||||
[Step.Step3_TablePreview]: TablePreview,
|
||||
[Step.Step4_Processing]: Processing,
|
||||
};
|
||||
|
||||
export interface DatabaseCreateFromExcelProps {
|
||||
onCancel: () => void;
|
||||
botId: string;
|
||||
spaceId: string;
|
||||
maxColumnNum?: number;
|
||||
onSave?: (params: {
|
||||
response: any;
|
||||
stateData: DatabaseInfo;
|
||||
}) => Promise<void>;
|
||||
}
|
||||
export const DatabaseCreateFromExcel: FC<
|
||||
DatabaseCreateFromExcelProps
|
||||
> = props => {
|
||||
const { onCancel, botId, onSave, maxColumnNum = 10, spaceId } = props;
|
||||
|
||||
const { step, reset } = useStepStore(state => ({
|
||||
step: state.step,
|
||||
reset: state.reset,
|
||||
}));
|
||||
|
||||
// init / reset store
|
||||
useEffect(() => {
|
||||
useInitialConfigStore.setState(() => ({
|
||||
onCancel,
|
||||
botId,
|
||||
spaceId,
|
||||
onSave,
|
||||
maxColumnNum,
|
||||
}));
|
||||
return () => {
|
||||
reset();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const Component = map[step];
|
||||
return (
|
||||
<div className={styles['create-from-excel-wrapper']}>
|
||||
<Steps
|
||||
type="basic"
|
||||
size="small"
|
||||
current={step - 1}
|
||||
className={styles.steps}
|
||||
>
|
||||
<Steps.Step title={I18n.t('db_table_0126_012')} />
|
||||
<Steps.Step title={I18n.t('db_table_0126_013')} />
|
||||
<Steps.Step title={I18n.t('db_table_0126_014')} />
|
||||
<Steps.Step title={I18n.t('db_table_0126_015')} />
|
||||
</Steps>
|
||||
<Component />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { StepFooter } from './components/step-footer';
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 { create } from 'zustand';
|
||||
import { noop } from 'lodash-es';
|
||||
import { type DatabaseInfo } from '@coze-studio/bot-detail-store';
|
||||
|
||||
export interface initialConfigStore {
|
||||
onCancel: () => void;
|
||||
botId: string;
|
||||
spaceId: string;
|
||||
maxColumnNum: number;
|
||||
onSave?: (params: {
|
||||
response: any;
|
||||
stateData: DatabaseInfo;
|
||||
}) => Promise<void>;
|
||||
}
|
||||
|
||||
// 用来存储静态状态,非初始化场景下,仅只读不可修改
|
||||
export const useInitialConfigStore = create<initialConfigStore>()(set => ({
|
||||
onCancel: noop,
|
||||
botId: '',
|
||||
spaceId: '',
|
||||
maxColumnNum: 10,
|
||||
}));
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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 { type StepState, type Step } from '../types';
|
||||
|
||||
export interface StepStore {
|
||||
step: Step;
|
||||
enableGoToNextStep: boolean;
|
||||
step1_upload: StepState.Upload;
|
||||
step2_tableStructure: StepState.TableStructure;
|
||||
step3_tablePreview: StepState.TablePreview;
|
||||
step4_processing: StepState.Processing;
|
||||
goToNextStep: () => void;
|
||||
backToPreviousStep: () => void;
|
||||
reset: () => void;
|
||||
set_step1_upload: (newState: StepState.Upload) => void;
|
||||
set_step2_tableStructure: (newState: StepState.TableStructure) => void;
|
||||
set_step3_tablePreview: (newState: StepState.TablePreview) => void;
|
||||
set_step4_processing: (newState: StepState.Processing) => void;
|
||||
set_enableGoToNextStep: (newState: boolean) => void;
|
||||
}
|
||||
|
||||
export const useStepStore = create<StepStore>()(
|
||||
devtools(set => ({
|
||||
step: 1,
|
||||
enableGoToNextStep: true,
|
||||
step1_upload: {},
|
||||
step2_tableStructure: {},
|
||||
step3_tablePreview: {},
|
||||
step4_processing: {},
|
||||
set_step1_upload: (newState: StepState.Upload) =>
|
||||
set(state => ({
|
||||
step1_upload: {
|
||||
...state.step1_upload,
|
||||
...newState,
|
||||
},
|
||||
})),
|
||||
set_step2_tableStructure: (newState: StepState.TableStructure) =>
|
||||
set(state => ({
|
||||
step2_tableStructure: {
|
||||
...state.step2_tableStructure,
|
||||
...newState,
|
||||
},
|
||||
})),
|
||||
set_step3_tablePreview: (newState: StepState.TablePreview) =>
|
||||
set(state => ({
|
||||
step3_tablePreview: {
|
||||
...state.step3_tablePreview,
|
||||
...newState,
|
||||
},
|
||||
})),
|
||||
set_step4_processing: (newState: StepState.Processing) =>
|
||||
set(state => ({
|
||||
step4_processing: {
|
||||
...state.step4_processing,
|
||||
...newState,
|
||||
},
|
||||
})),
|
||||
goToNextStep: () =>
|
||||
set(state => ({
|
||||
step: state.step + 1,
|
||||
})),
|
||||
backToPreviousStep: () =>
|
||||
set(state => ({
|
||||
step: state.step - 1,
|
||||
})),
|
||||
reset: () =>
|
||||
set({
|
||||
step: 1,
|
||||
enableGoToNextStep: true,
|
||||
step1_upload: {},
|
||||
step2_tableStructure: {},
|
||||
step3_tablePreview: {},
|
||||
step4_processing: {},
|
||||
}),
|
||||
set_enableGoToNextStep: (newState: boolean) => {
|
||||
set(() => ({ enableGoToNextStep: newState }));
|
||||
},
|
||||
})),
|
||||
);
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type DatabaseInfo } from '@coze-studio/bot-detail-store';
|
||||
import { type FileItem } from '@coze-arch/bot-semi/Upload';
|
||||
|
||||
import { type SheetInfo } from './datamodel';
|
||||
|
||||
export type Callback =
|
||||
| (() => Promise<boolean> | boolean)
|
||||
| (() => Promise<void> | void);
|
||||
|
||||
export enum Step {
|
||||
Step1_Upload = 1,
|
||||
Step2_TableStructure = 2,
|
||||
Step3_TablePreview = 3,
|
||||
Step4_Processing = 4,
|
||||
}
|
||||
|
||||
export interface SheetItem {
|
||||
id: number;
|
||||
sheet_name: string;
|
||||
total_row: number;
|
||||
}
|
||||
|
||||
export interface ExcelValue {
|
||||
sheetID: number;
|
||||
headerRow: number;
|
||||
dataStartRow: number;
|
||||
}
|
||||
|
||||
export type Row = Record<number, string>;
|
||||
|
||||
export declare namespace StepState {
|
||||
export interface Upload {
|
||||
fileList?: FileItem[];
|
||||
}
|
||||
export interface TableStructure {
|
||||
excelBasicInfo?: SheetItem[];
|
||||
excelValue?: ExcelValue;
|
||||
tableValue?: DatabaseInfo;
|
||||
}
|
||||
export interface TablePreview {
|
||||
previewData?: SheetInfo;
|
||||
}
|
||||
export interface Processing {
|
||||
tableID?: string;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.modal-container {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.modal-table-btn {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.modal-temp {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.modal-temp-right {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
height: 409px;
|
||||
margin-bottom: 24px;
|
||||
padding: 15px 24px;
|
||||
|
||||
background: rgba(255, 255, 255, 100%);
|
||||
border: 1px solid rgba(29, 28, 35, 8%);
|
||||
border-radius: 12px;
|
||||
|
||||
.modal-temp-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: rgba(29, 28, 35, 100%);
|
||||
}
|
||||
|
||||
.modal-temp-image {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.modal-temp-description {
|
||||
height: 64px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: rgba(29, 28, 35, 80%);
|
||||
}
|
||||
|
||||
.modal-temp-btn-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
margin-top: 16px;
|
||||
padding: 8px 0;
|
||||
|
||||
.modal-temp-btn {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-temp-preview {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 329px;
|
||||
padding: 16px 21px 24px;
|
||||
|
||||
background: rgba(241, 242, 253, 100%);
|
||||
border-radius: 11px 11px 0 0;
|
||||
|
||||
.title {
|
||||
padding-bottom: 12px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
color: rgba(77, 83, 232, 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-modify-tips {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
margin-bottom: 14px;
|
||||
padding: 12px;
|
||||
|
||||
background: var(--light-usage-warning-light-color-warning-light-default,
|
||||
#fff8ea);
|
||||
|
||||
.tip-icon {
|
||||
margin: 0 20px 0 17px;
|
||||
color: rgba(252, 136, 0, 100%);
|
||||
|
||||
>svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 65px;
|
||||
}
|
||||
|
||||
.link {
|
||||
cursor: pointer;
|
||||
margin-left: 20px;
|
||||
color: #3370ff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
// 新增样式 @zhangyuanzhou.zyz
|
||||
.entry {
|
||||
display: flex;
|
||||
gap: 64px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 409px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
background: rgba(255, 255, 255, 100%);
|
||||
border: 1px solid rgba(29, 28, 35, 8%);
|
||||
border-radius: 12px;
|
||||
|
||||
.entry-method {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
color: var(--Light-usage-text---color-text-0, #1d1c23);
|
||||
|
||||
.entry-method-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.entry-method-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.entry-method:hover {
|
||||
color: var(--Light-color-brand---brand-5, #4d53e8);
|
||||
}
|
||||
}
|
||||
|
||||
// 当窗口足够大时,高度固定为641px
|
||||
// 当窗口太小时,高度随vh变化,333px为内容区距离视窗边缘的距离
|
||||
.database-table-structure-container {
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
height: min(641px, calc(100vh - 333px));
|
||||
}
|
||||
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.generate-ai-popover-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
width: 560px;
|
||||
padding: 24px;
|
||||
|
||||
background-color: rgba(247, 247, 250, 100%);
|
||||
border-radius: 12px;
|
||||
|
||||
.title {
|
||||
margin-bottom: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.text-area {
|
||||
:global {
|
||||
.semi-input-textarea {
|
||||
overflow: auto;
|
||||
max-height: 191px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
:global {
|
||||
.semi-popover {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.semi-popover-content {
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-close-button {
|
||||
height: 24px !important;
|
||||
padding: 4px !important;
|
||||
border-radius: 3px !important;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(46, 50, 56, 5%) !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,678 @@
|
||||
/*
|
||||
* 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 complexity */
|
||||
import { useRef, useMemo, useEffect, useState } from 'react';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useLocalStorageState } from 'ahooks';
|
||||
import { IconAlertTriangle, IconClose } from '@douyinfe/semi-icons';
|
||||
import { type DatabaseInfo } from '@coze-studio/bot-detail-store';
|
||||
import { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { I18n, getUnReactiveLanguage } from '@coze-arch/i18n';
|
||||
import { sendTeaEvent, EVENT_NAMES } from '@coze-arch/bot-tea';
|
||||
import {
|
||||
Button,
|
||||
Image,
|
||||
Popconfirm,
|
||||
Divider,
|
||||
Icon,
|
||||
TextArea,
|
||||
Popover,
|
||||
UIButton,
|
||||
Modal,
|
||||
Form,
|
||||
Toast,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { IconWarningSize24 } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
BotTableRWMode,
|
||||
type RecommendDataModelResponse,
|
||||
SceneType,
|
||||
FieldItemType,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
|
||||
import {
|
||||
DatabaseTableStructure,
|
||||
type DatabaseTableStructureRef,
|
||||
} from '../database-table-structure';
|
||||
import { DatabaseCreateFromExcel } from '../database-create-from-excel';
|
||||
import { BotDebugButton } from '../bot-debug-button';
|
||||
import { CreateType, type OnSave, type NL2DBInfo } from '../../types';
|
||||
import { useCreateFromExcelFG } from '../../hooks/use-create-from-excel-fg';
|
||||
import { TEMPLATE_INFO } from '../../const';
|
||||
import tableTempEN from '../../assets/table-template-en.png';
|
||||
import tableTempCN from '../../assets/table-template-cn.png';
|
||||
import tablePreviewEN from '../../assets/table-preview-en.png';
|
||||
import tablePreviewCN from '../../assets/table-preview-cn.png';
|
||||
import { ReactComponent as UpArrowSVG } from '../../assets/icon_up-arrow.svg';
|
||||
import { ReactComponent as DownArrowSvg } from '../../assets/icon_down-arrow.svg';
|
||||
import { ReactComponent as AddSVG } from '../../assets/icon_add_outlined.svg';
|
||||
import { ReactComponent as GenerateSVG } from '../../assets/generate.svg';
|
||||
import { ReactComponent as FileSVG } from '../../assets/file.svg';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface ExpertModeConfig {
|
||||
isExpertMode: boolean;
|
||||
maxTableNum: number;
|
||||
maxColumnNum: number;
|
||||
readAndWriteModes: BotTableRWMode[];
|
||||
}
|
||||
|
||||
export interface DatabaseModalProps {
|
||||
visible: boolean;
|
||||
onCancel: () => void;
|
||||
database: DatabaseInfo;
|
||||
botId: string;
|
||||
spaceId: string;
|
||||
readonly: boolean;
|
||||
NL2DBInfo: NL2DBInfo | null;
|
||||
expertModeConfig?: ExpertModeConfig;
|
||||
onSave?: OnSave;
|
||||
}
|
||||
|
||||
export const DatabaseModal: React.FC<DatabaseModalProps> = props => {
|
||||
const {
|
||||
database,
|
||||
botId,
|
||||
spaceId,
|
||||
readonly,
|
||||
onCancel,
|
||||
onSave,
|
||||
NL2DBInfo,
|
||||
expertModeConfig,
|
||||
visible,
|
||||
} = props;
|
||||
|
||||
const [generateTableLoading, setGenerateTableLoading] = useState(false);
|
||||
const [contentCheckErrorMsg, setContentCheckErrorMsg] = useState<string>('');
|
||||
const [isEntry, setIsEntry] = useState<boolean>(
|
||||
!database.tableId && !NL2DBInfo,
|
||||
);
|
||||
const [isPreview, setIsPreview] = useState<boolean>(false);
|
||||
const [isDeletedField, setIsDeletedField] = useState<boolean>(false);
|
||||
const [
|
||||
shouldHideDatabaseTableStructureTipsForCurrent,
|
||||
setShouldHideDatabaseTableStructureTipsForCurrent,
|
||||
] = useState<boolean>(false);
|
||||
const [createType, setCreateType] = useState<CreateType>(
|
||||
NL2DBInfo ? CreateType.recommend : CreateType.custom,
|
||||
);
|
||||
const [data, setData] = useState<DatabaseInfo>({
|
||||
tableId: '',
|
||||
name: '',
|
||||
desc: '',
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: [],
|
||||
});
|
||||
const [AIPopoverVisible, setAIPopoverVisible] = useState(false);
|
||||
|
||||
const nlTextAreaRef = useRef<HTMLTextAreaElement>();
|
||||
const tableStructureRef = useRef<DatabaseTableStructureRef>();
|
||||
const enableCreateFromExcel = useCreateFromExcelFG();
|
||||
const [
|
||||
mapOfShouldHidingDatabaseTableStructureTips,
|
||||
setMapOfShouldHidingDatabaseTableStructureTips,
|
||||
] = useLocalStorageState<string | undefined>(
|
||||
// FIXME: 此属性名意义不明确,处为了兼容,暂不修改此属性名,但后续需要使用更明确的命名
|
||||
'use-local-storage-state-modify-tips',
|
||||
{
|
||||
defaultValue: '',
|
||||
},
|
||||
);
|
||||
|
||||
const language = getUnReactiveLanguage();
|
||||
const isEdit = Boolean(data.tableId);
|
||||
const [isReadonly, setIsReadonly] = useState(false);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setIsReadonly(true);
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
await tableStructureRef.current.submit();
|
||||
} finally {
|
||||
setIsReadonly(false);
|
||||
}
|
||||
};
|
||||
|
||||
const hideTableStructureTips = useMemo(() => {
|
||||
const lsMap = JSON.parse(
|
||||
mapOfShouldHidingDatabaseTableStructureTips || '{}',
|
||||
);
|
||||
|
||||
return (
|
||||
!isEdit ||
|
||||
lsMap?.[botId] ||
|
||||
shouldHideDatabaseTableStructureTipsForCurrent
|
||||
);
|
||||
}, [
|
||||
isEdit,
|
||||
shouldHideDatabaseTableStructureTipsForCurrent,
|
||||
mapOfShouldHidingDatabaseTableStructureTips,
|
||||
]);
|
||||
|
||||
const title = useMemo(() => {
|
||||
if (isEdit) {
|
||||
return I18n.t('db_edit_title');
|
||||
}
|
||||
if (createType === CreateType.excel) {
|
||||
return I18n.t('db_table_0126_011');
|
||||
}
|
||||
return I18n.t('db_add_table_title');
|
||||
}, [isEdit, createType]);
|
||||
|
||||
const showEntry = isEntry && !isEdit && !NL2DBInfo;
|
||||
const shouldShowAIGenerate =
|
||||
/**
|
||||
* 1. 入口不展示
|
||||
* 2. 编辑态不展示
|
||||
* 3. Excel导入时不展示
|
||||
*/
|
||||
!showEntry && !isEdit && createType !== CreateType.excel;
|
||||
|
||||
const setDataToDefault = () => {
|
||||
setData({
|
||||
name: '',
|
||||
desc: '',
|
||||
tableId: '',
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: [
|
||||
{
|
||||
nanoid: nanoid(),
|
||||
name: '',
|
||||
desc: '',
|
||||
type: FieldItemType.Text,
|
||||
must_required: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const setTableFieldsListToDefault = () => {
|
||||
tableStructureRef.current?.setTableFieldsList([
|
||||
{
|
||||
nanoid: nanoid(),
|
||||
name: '',
|
||||
desc: '',
|
||||
type: FieldItemType.Text,
|
||||
must_required: false,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const onUseTemplate = () => {
|
||||
setCreateType(CreateType.template);
|
||||
setIsEntry(false);
|
||||
setData({
|
||||
...TEMPLATE_INFO,
|
||||
});
|
||||
};
|
||||
|
||||
const onUseCustom = () => {
|
||||
setCreateType(CreateType.custom);
|
||||
setIsEntry(false);
|
||||
setDataToDefault();
|
||||
};
|
||||
|
||||
const onUseExcel = () => {
|
||||
setCreateType(CreateType.excel);
|
||||
setIsEntry(false);
|
||||
};
|
||||
|
||||
const generateTableByNL = async (text: string, type: SceneType) => {
|
||||
setGenerateTableLoading(true);
|
||||
let res: RecommendDataModelResponse | undefined;
|
||||
try {
|
||||
res = await MemoryApi.RecommendDataModel({
|
||||
bot_id: botId,
|
||||
scene_type: type,
|
||||
text,
|
||||
});
|
||||
} catch (error) {
|
||||
setGenerateTableLoading(false);
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseNL2DB,
|
||||
error: error as Error,
|
||||
});
|
||||
setDataToDefault();
|
||||
setTableFieldsListToDefault();
|
||||
}
|
||||
|
||||
if (res?.bot_table_list?.[0]) {
|
||||
if (type === SceneType.BotPersona) {
|
||||
setCreateType(CreateType.recommend);
|
||||
}
|
||||
if (type === SceneType.ModelDesc) {
|
||||
setCreateType(CreateType.naturalLanguage);
|
||||
}
|
||||
setData({
|
||||
tableId: '',
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
name: res.bot_table_list[0].table_name,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
desc: res.bot_table_list[0].table_desc,
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableMemoryList: res.bot_table_list[0].field_list.map(i => ({
|
||||
name: i.name,
|
||||
desc: i.desc,
|
||||
must_required: i.must_required,
|
||||
type: i.type,
|
||||
nanoid: nanoid(),
|
||||
id: Number(i.id),
|
||||
})),
|
||||
});
|
||||
|
||||
// data 是初始值,此处需要手动 setState 更新子组件状态
|
||||
// 若 Modal 已提前关闭,子组件卸载,则 ref 为空,需要加上可选链判断一下
|
||||
tableStructureRef.current?.setTableFieldsList(
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
res.bot_table_list[0].field_list.map(i => ({
|
||||
name: i.name,
|
||||
desc: i.desc,
|
||||
must_required: i.must_required,
|
||||
type: i.type,
|
||||
nanoid: nanoid(),
|
||||
id: Number(i.id),
|
||||
})),
|
||||
);
|
||||
} else {
|
||||
if (type === SceneType.BotPersona) {
|
||||
Toast.info(I18n.t('recommended_failed'));
|
||||
setDataToDefault();
|
||||
setTableFieldsListToDefault();
|
||||
}
|
||||
if (type === SceneType.ModelDesc) {
|
||||
Toast.warning(I18n.t('generate_failed'));
|
||||
setAIPopoverVisible(true);
|
||||
}
|
||||
}
|
||||
setGenerateTableLoading(false);
|
||||
};
|
||||
|
||||
const handleGenerate = () => {
|
||||
const generate = () => {
|
||||
const { value } = nlTextAreaRef.current || {};
|
||||
if (value) {
|
||||
generateTableByNL(value, SceneType.ModelDesc);
|
||||
}
|
||||
};
|
||||
|
||||
sendTeaEvent(EVENT_NAMES.generate_with_ai_click, {
|
||||
bot_id: botId,
|
||||
need_login: true,
|
||||
have_access: true,
|
||||
});
|
||||
setAIPopoverVisible(false);
|
||||
|
||||
if (
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableStructureRef.current.tableFieldsList.filter(i => i.name).length > 0
|
||||
) {
|
||||
Modal.warning({
|
||||
title: I18n.t('bot_database_ai_replace'),
|
||||
content: I18n.t('bot_database_ai_replace_detailed'),
|
||||
okButtonProps: {
|
||||
type: 'warning',
|
||||
},
|
||||
onOk: () => {
|
||||
generate();
|
||||
},
|
||||
maskClosable: false,
|
||||
icon: <IconWarningSize24 />,
|
||||
});
|
||||
} else {
|
||||
generate();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setAIPopoverVisible(false);
|
||||
setIsPreview(false);
|
||||
setShouldHideDatabaseTableStructureTipsForCurrent(false);
|
||||
setIsDeletedField(false);
|
||||
setContentCheckErrorMsg('');
|
||||
setIsEntry(true);
|
||||
}, [visible]);
|
||||
|
||||
useEffect(() => {
|
||||
setData(database);
|
||||
}, [database]);
|
||||
|
||||
useEffect(() => {
|
||||
setCreateType(NL2DBInfo ? CreateType.recommend : CreateType.custom);
|
||||
}, [NL2DBInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (NL2DBInfo && visible) {
|
||||
generateTableByNL(NL2DBInfo.prompt, SceneType.BotPersona);
|
||||
}
|
||||
}, [NL2DBInfo, visible]);
|
||||
|
||||
const DefaultFooter = (
|
||||
<>
|
||||
{contentCheckErrorMsg ? (
|
||||
<Form.ErrorMessage error={contentCheckErrorMsg} />
|
||||
) : null}
|
||||
{hideTableStructureTips ? null : (
|
||||
<div className={s['modal-modify-tips']}>
|
||||
<div className={s.description}>
|
||||
<IconAlertTriangle className={s['tip-icon']} />
|
||||
<span style={{ textAlign: 'left' }}>{I18n.t('db_edit_tips1')}</span>
|
||||
<span
|
||||
className={s.link}
|
||||
onClick={() => {
|
||||
const lsMap = JSON.parse(
|
||||
mapOfShouldHidingDatabaseTableStructureTips || '{}',
|
||||
);
|
||||
lsMap[botId] = true;
|
||||
setMapOfShouldHidingDatabaseTableStructureTips(
|
||||
JSON.stringify(lsMap),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{I18n.t('db_edit_tips2')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<IconClose
|
||||
onClick={() =>
|
||||
setShouldHideDatabaseTableStructureTipsForCurrent(true)
|
||||
}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={s['modal-table-btn']}>
|
||||
{isDeletedField ? (
|
||||
<Popconfirm
|
||||
title={I18n.t('db_del_field_confirm_title')}
|
||||
content={I18n.t('db_del_field_confirm_info')}
|
||||
okText={I18n.t('db_del_field_confirm_yes')}
|
||||
cancelText={I18n.t('db_del_field_confirm_no')}
|
||||
okType="danger"
|
||||
onConfirm={handleSave}
|
||||
>
|
||||
<BotDebugButton
|
||||
loading={isReadonly}
|
||||
theme="solid"
|
||||
type="primary"
|
||||
readonly={readonly}
|
||||
>
|
||||
{I18n.t('db_edit_save')}
|
||||
</BotDebugButton>
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<BotDebugButton
|
||||
readonly={readonly}
|
||||
loading={isReadonly}
|
||||
theme="solid"
|
||||
type="primary"
|
||||
onClick={handleSave}
|
||||
>
|
||||
{I18n.t('db_edit_save')}
|
||||
</BotDebugButton>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const Entry = (
|
||||
<div className={s['modal-temp']}>
|
||||
<div className={s.entry}>
|
||||
<div
|
||||
className={s['entry-method']}
|
||||
onClick={onUseCustom}
|
||||
data-testid={BotE2e.BotDatabaseAddModalAddCustomBtn}
|
||||
>
|
||||
<Icon svg={<AddSVG />} className={s['entry-method-icon']} />
|
||||
<span className={s['entry-method-title']}>
|
||||
{I18n.t('db_add_table_cust')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{enableCreateFromExcel ? (
|
||||
<Divider
|
||||
layout="vertical"
|
||||
style={{
|
||||
height: '32px',
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{enableCreateFromExcel ? (
|
||||
<div className={s['entry-method']} onClick={onUseExcel}>
|
||||
<Icon svg={<FileSVG />} className={s['entry-method-icon']} />
|
||||
<span className={s['entry-method-title']}>
|
||||
{I18n.t('db_table_0126_010')}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={s['modal-temp-right']}>
|
||||
<div
|
||||
className={s['modal-temp-title']}
|
||||
data-testid={BotE2e.BotDatabaseAddModalTemplateTitle}
|
||||
>
|
||||
{I18n.t('db_add_table_temp_title')}
|
||||
</div>
|
||||
<Image
|
||||
className={s['modal-temp-image']}
|
||||
height={201}
|
||||
src={language === 'zh-CN' ? tableTempCN : tableTempEN}
|
||||
/>
|
||||
<div className={s['modal-temp-description']}>
|
||||
💡{I18n.t('db_add_table_temp_tips')}
|
||||
</div>
|
||||
{isPreview ? (
|
||||
<div className={s['modal-temp-preview']}>
|
||||
<div className={s.title}>
|
||||
{I18n.t('db_add_table_temp_preview_tips')}
|
||||
</div>
|
||||
<Image
|
||||
height={239}
|
||||
src={language === 'zh-CN' ? tablePreviewCN : tablePreviewEN}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={s['modal-temp-btn-group']}>
|
||||
<Button
|
||||
data-testid={BotE2e.BotDatabaseAddModalPreviewTemplateBtn}
|
||||
theme="light"
|
||||
type="tertiary"
|
||||
onClick={() => setIsPreview(state => !state)}
|
||||
className={s['modal-temp-btn']}
|
||||
>
|
||||
{I18n.t('db_add_table_temp_preview')}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid={BotE2e.BotDatabaseAddModalUseTemplateBtn}
|
||||
theme="solid"
|
||||
type="primary"
|
||||
onClick={onUseTemplate}
|
||||
className={s['modal-temp-btn']}
|
||||
>
|
||||
{I18n.t('db_add_table_temp_use')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const getFooter = () => {
|
||||
if (showEntry) {
|
||||
return null;
|
||||
}
|
||||
if (createType === CreateType.excel) {
|
||||
return null;
|
||||
}
|
||||
return DefaultFooter;
|
||||
};
|
||||
|
||||
const getContent = () => {
|
||||
if (showEntry) {
|
||||
return Entry;
|
||||
}
|
||||
if (createType === CreateType.excel) {
|
||||
return (
|
||||
<DatabaseCreateFromExcel
|
||||
onCancel={onCancel}
|
||||
botId={botId}
|
||||
onSave={onSave}
|
||||
spaceId={spaceId}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
maxColumnNum={expertModeConfig.maxColumnNum}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s['database-table-structure-container']}>
|
||||
<DatabaseTableStructure
|
||||
data={data}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
ref={tableStructureRef}
|
||||
loading={generateTableLoading}
|
||||
loadingTips={I18n.t('bot_database_ai_waiting')}
|
||||
botId={botId}
|
||||
readAndWriteModeOptions={
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
expertModeConfig.isExpertMode ? 'expert' : 'normal'
|
||||
}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
maxColumnNum={expertModeConfig.maxColumnNum}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
onDeleteField={list => {
|
||||
setIsDeletedField(
|
||||
!database.tableMemoryList.every(i =>
|
||||
// TODO: 当前field id生成规则有问题,故暂时使用 nanoid 替换
|
||||
list.find(j => j.nanoid === i.nanoid),
|
||||
),
|
||||
);
|
||||
}}
|
||||
createType={createType}
|
||||
setContentCheckErrorMsg={setContentCheckErrorMsg}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
closable={false}
|
||||
width={1138}
|
||||
centered
|
||||
footer={getFooter()}
|
||||
title={
|
||||
<div className={s['title-wrapper']}>
|
||||
<div data-testid={BotE2e.BotDatabaseAddModalTitle}>{title}</div>
|
||||
<div className={s.right}>
|
||||
{shouldShowAIGenerate ? (
|
||||
<Popover
|
||||
trigger="custom"
|
||||
position="bottomRight"
|
||||
content={
|
||||
<div className={s['generate-ai-popover-wrapper']}>
|
||||
<div
|
||||
className={s.title}
|
||||
data-testid={
|
||||
BotE2e.BotDatabaseAddModalTitleCreateAiModalTitle
|
||||
}
|
||||
>
|
||||
{I18n.t('bot_database_ai_create')}
|
||||
</div>
|
||||
<TextArea
|
||||
data-testid={
|
||||
BotE2e.BotDatabaseAddModalTitleCreateAiModalDesc
|
||||
}
|
||||
autosize
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
ref={nlTextAreaRef}
|
||||
rows={1}
|
||||
placeholder={I18n.t('bot_database_ai_create_tip')}
|
||||
className={s['text-area']}
|
||||
/>
|
||||
<div className={s['button-wrapper']}>
|
||||
<UIButton
|
||||
data-testid={
|
||||
BotE2e.BotDatabaseAddModalTitleCreateAiModalCreateBtn
|
||||
}
|
||||
theme="borderless"
|
||||
onClick={handleGenerate}
|
||||
icon={<Icon svg={<GenerateSVG />} />}
|
||||
>
|
||||
{I18n.t('bot_database_ai_generate')}
|
||||
</UIButton>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
keepDOM
|
||||
visible={AIPopoverVisible}
|
||||
onVisibleChange={_v => {
|
||||
setAIPopoverVisible(_v);
|
||||
}}
|
||||
onClickOutSide={() => {
|
||||
setAIPopoverVisible(false);
|
||||
}}
|
||||
className={s.popover}
|
||||
>
|
||||
<UIButton
|
||||
data-testid={BotE2e.BotDatabaseAddModalTitleCreateAiBtn}
|
||||
theme="borderless"
|
||||
icon={
|
||||
AIPopoverVisible ? (
|
||||
<Icon svg={<UpArrowSVG />} />
|
||||
) : (
|
||||
<Icon svg={<DownArrowSvg />} />
|
||||
)
|
||||
}
|
||||
iconPosition="right"
|
||||
onClick={() => {
|
||||
sendTeaEvent(EVENT_NAMES.nl2table_create_table_click, {
|
||||
bot_id: botId,
|
||||
need_login: true,
|
||||
have_access: true,
|
||||
});
|
||||
setAIPopoverVisible(true);
|
||||
}}
|
||||
>
|
||||
{I18n.t('bot_database_ai_create')}
|
||||
</UIButton>
|
||||
</Popover>
|
||||
) : null}
|
||||
<UIButton
|
||||
data-testid={BotE2e.BotDatabaseAddModalTitleCloseIcon}
|
||||
icon={<IconClose />}
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
onClick={onCancel}
|
||||
className={s['modal-close-button']}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
maskClosable={false}
|
||||
>
|
||||
<div className={s['modal-container']}>{getContent()}</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
.column-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.table-header-label-tooltip-icon {
|
||||
cursor: pointer;
|
||||
margin-left: 8px;
|
||||
color: rgba(198, 202, 205, 100%);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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, type ReactNode } from 'react';
|
||||
|
||||
import { Icon, Popover } from '@coze-arch/bot-semi';
|
||||
|
||||
import { ReactComponent as InfoSVG } from '../../../../assets/icon_info_outlined.svg';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export const ColumnHeader: FC<{
|
||||
label: string;
|
||||
required: boolean;
|
||||
tips: ReactNode;
|
||||
}> = p => {
|
||||
const { label, required, tips } = p;
|
||||
return (
|
||||
<div className={s['column-title']}>
|
||||
<span>{label}</span>
|
||||
{required ? <span style={{ color: 'red' }}>*</span> : null}
|
||||
<Popover showArrow position="top" content={<div>{tips}</div>}>
|
||||
<Icon
|
||||
svg={<InfoSVG />}
|
||||
className={s['table-header-label-tooltip-icon']}
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.modal-key-tip {
|
||||
width: 200px !important;
|
||||
|
||||
ul {
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 { PopoverContent } from '@coze-studio/components';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export const KeyTipsNode: React.FC = () => (
|
||||
<PopoverContent className={s['modal-key-tip']}>{`- ${I18n.t(
|
||||
'db_add_table_field_name_tips1',
|
||||
)}
|
||||
- ${I18n.t('db_add_table_field_name_tips2')}
|
||||
- ${I18n.t('db_add_table_field_name_tips3')}
|
||||
- ${I18n.t('db_add_table_field_name_tips4')}`}</PopoverContent>
|
||||
);
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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 complexity */
|
||||
import { type TableMemoryItem } from '@coze-studio/bot-detail-store';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { type MapperItem, type TriggerType, VerifyType } from '../../../types';
|
||||
|
||||
// 校验 Table Name 和 Field Name
|
||||
const namingRegexMapper = [
|
||||
{
|
||||
type: 1,
|
||||
regex: /[^a-z0-9_]/,
|
||||
errorMsg: I18n.t('db_add_table_field_name_tips2'),
|
||||
},
|
||||
{
|
||||
type: 2,
|
||||
regex: /^[^a-z]/,
|
||||
errorMsg: I18n.t('db_add_table_field_name_tips3'),
|
||||
},
|
||||
{
|
||||
type: 3,
|
||||
regex: /[\s\S]{64,}/,
|
||||
errorMsg: I18n.t('db_add_table_field_name_tips4'),
|
||||
},
|
||||
];
|
||||
export const validateNaming = (str: string, errList: string[] = []) => {
|
||||
let list = [...errList];
|
||||
namingRegexMapper.forEach(i => {
|
||||
list = list.filter(j => j !== i.errorMsg);
|
||||
if (i.regex.test(str || '')) {
|
||||
list.push(i.errorMsg);
|
||||
}
|
||||
});
|
||||
return list;
|
||||
};
|
||||
|
||||
// 校验 Table Fields
|
||||
export const thMapper: MapperItem[] = [
|
||||
{
|
||||
label: I18n.t('db_add_table_field_name'),
|
||||
key: 'name',
|
||||
validator: [
|
||||
{
|
||||
type: VerifyType.Naming,
|
||||
message: '',
|
||||
},
|
||||
{
|
||||
type: VerifyType.Required,
|
||||
message: I18n.t('db_table_save_exception_nofieldname'),
|
||||
},
|
||||
{
|
||||
type: VerifyType.Unique,
|
||||
message: I18n.t('db_table_save_exception_fieldname'),
|
||||
},
|
||||
],
|
||||
defaultValue: '',
|
||||
require: true,
|
||||
},
|
||||
{
|
||||
label: I18n.t('db_add_table_field_desc'),
|
||||
key: 'desc',
|
||||
require: false,
|
||||
validator: [],
|
||||
defaultValue: '',
|
||||
},
|
||||
{
|
||||
label: I18n.t('db_add_table_field_type'),
|
||||
key: 'type',
|
||||
require: true,
|
||||
validator: [
|
||||
{
|
||||
type: VerifyType.Required,
|
||||
message: I18n.t('db_table_save_exception_fieldtype'),
|
||||
},
|
||||
],
|
||||
defaultValue: '',
|
||||
},
|
||||
{
|
||||
label: I18n.t('db_add_table_field_necessary'),
|
||||
key: 'must_required',
|
||||
require: false,
|
||||
validator: [],
|
||||
defaultValue: true,
|
||||
},
|
||||
];
|
||||
export const validateFields = (
|
||||
list: TableMemoryItem[],
|
||||
trigger: TriggerType,
|
||||
) => {
|
||||
const resList = list.map(_listItem => {
|
||||
const listItem: TableMemoryItem = { ..._listItem };
|
||||
thMapper.forEach(thItem => {
|
||||
const thKey = thItem.key as keyof TableMemoryItem;
|
||||
|
||||
thItem.validator.forEach(verifyItem => {
|
||||
if (!listItem?.errorMapper) {
|
||||
listItem.errorMapper = {};
|
||||
}
|
||||
let errTarget = listItem?.errorMapper?.[thKey];
|
||||
const value = listItem[thKey];
|
||||
if (!errTarget) {
|
||||
listItem.errorMapper[thKey] = [];
|
||||
errTarget = [];
|
||||
}
|
||||
const msg = verifyItem.message;
|
||||
switch (verifyItem.type) {
|
||||
case VerifyType.Required: {
|
||||
// 报错出现时机:点击保存按钮时,出现提示。表中某一行填写了数据,但是未填写必填字段时,需要报错
|
||||
if (
|
||||
trigger === 'save' &&
|
||||
!value &&
|
||||
thMapper.find(
|
||||
i =>
|
||||
!!listItem[i.key as keyof TableMemoryItem] && !i.defaultValue,
|
||||
)
|
||||
) {
|
||||
listItem.errorMapper[thKey].push(msg);
|
||||
}
|
||||
// 报错消失时机:必填输入框输入了内容后,报错立刻消失
|
||||
if (trigger === 'change' && value) {
|
||||
listItem.errorMapper[thKey] = errTarget.filter(i => i !== msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VerifyType.Unique: {
|
||||
// 报错出现时机:点击保存按钮时,出现提示。
|
||||
if (
|
||||
trigger === 'save' &&
|
||||
value &&
|
||||
list.filter(i => i[thKey] === listItem[thKey]).length !== 1
|
||||
) {
|
||||
listItem.errorMapper[thKey].push(msg);
|
||||
}
|
||||
// 报错消失时机:必填输入框输入了内容后,报错立刻消失
|
||||
if (
|
||||
trigger === 'change' &&
|
||||
value &&
|
||||
list.filter(i => i[thKey] === listItem[thKey]).length === 1
|
||||
) {
|
||||
listItem.errorMapper[thKey] = errTarget.filter(i => i !== msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VerifyType.Naming: {
|
||||
// 报错出现时机:命名格式有问题,失去焦点时,立刻校验格式
|
||||
if (
|
||||
trigger === 'save' ||
|
||||
trigger === 'blur' ||
|
||||
(trigger === 'change' && errTarget.length)
|
||||
) {
|
||||
listItem.errorMapper[thKey] = validateNaming(
|
||||
value as string,
|
||||
errTarget,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
listItem.errorMapper[thKey] = Array.from(
|
||||
new Set(listItem.errorMapper[thKey]),
|
||||
);
|
||||
});
|
||||
});
|
||||
return listItem;
|
||||
});
|
||||
|
||||
return resList;
|
||||
};
|
||||
@@ -0,0 +1,205 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
/* stylelint-disable no-duplicate-selectors */
|
||||
.th-tip-name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.th-tip-dot {
|
||||
position: relative;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.th-tip-dot::before {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 0;
|
||||
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
|
||||
background-color: #000;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 新增样式 @zhangyuanzhou.zyz
|
||||
.form-input-error {
|
||||
:global {
|
||||
.semi-input-wrapper {
|
||||
border-color: #f93920
|
||||
}
|
||||
|
||||
.semi-input-wrapper:hover {
|
||||
border-color: var(--semi-color-border, rgba(29, 28, 35, 8%))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.table-structure-form {
|
||||
padding-bottom: 24px;
|
||||
|
||||
:global {
|
||||
|
||||
// 去除 error message icon
|
||||
.semi-form-field-validate-status-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.semi-form-field-validate-status-icon+span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.semi-col-12 {
|
||||
.semi-form-field[x-field-id="prompt_disabled"] {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.semi-form-field-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.max-row-banner {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.form-item-label-tooltip-icon {
|
||||
cursor: pointer;
|
||||
color: rgba(198, 202, 205, 100%);
|
||||
}
|
||||
|
||||
.table-structure-table {
|
||||
:global {
|
||||
|
||||
// table 圆角和边框
|
||||
.semi-table-container {
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--Light-usage-border---color-border-1, rgba(29, 28, 35, 8%));
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.semi-table-header {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
// hover时去除背景色
|
||||
.semi-table-body {
|
||||
.semi-table-row:hover {
|
||||
>.semi-table-row-cell {
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
.semi-table-row {
|
||||
cursor: default !important;
|
||||
|
||||
>.semi-table-row-cell {
|
||||
padding: 12px 8px !important;
|
||||
border-bottom: 1px solid transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
.semi-table-body {
|
||||
overflow: visible;
|
||||
max-height: none !important;
|
||||
padding: 12px 0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
||||
// 移除 UITable 中的 before元素影响
|
||||
.semi-table-row-cell::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.table-structure-table-wrapper {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.table-empty-tips {
|
||||
padding: 8px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: @error-red;
|
||||
}
|
||||
|
||||
|
||||
.spin {
|
||||
:global {
|
||||
.semi-spin-wrapper div {
|
||||
font-size: 16px;
|
||||
color: rgba(29, 28, 35, 35%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-setting-option {
|
||||
:global {
|
||||
.semi-select-option-selected {
|
||||
.semi-select-option-icon {
|
||||
color: #4D53E8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.table-name-form-field {
|
||||
padding-bottom: 24px !important;
|
||||
|
||||
// semi 原有的 disable 样式不满足需求,需要更明显的文字颜色
|
||||
:global {
|
||||
.semi-input-wrapper-disabled {
|
||||
-webkit-text-fill-color: rgba(29, 28, 35, 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-desc-form-field {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
.prompt_disabled_popover {
|
||||
:global {
|
||||
.semi-popover-icon-arrow {
|
||||
right: 8px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.read_mode_popover {
|
||||
:global {
|
||||
.semi-popover-icon-arrow {
|
||||
left: 8px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,859 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
useRef,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useState,
|
||||
type FC,
|
||||
type MutableRefObject,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import { noop } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { useBoolean } from 'ahooks';
|
||||
import { type DatabaseInfo } from '@coze-studio/bot-detail-store';
|
||||
import { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import {
|
||||
UITable,
|
||||
UITableAction,
|
||||
Button,
|
||||
Switch,
|
||||
Tooltip,
|
||||
Image,
|
||||
Form,
|
||||
Banner,
|
||||
withField,
|
||||
Popover,
|
||||
Spin,
|
||||
Icon,
|
||||
Row,
|
||||
Col,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { IconAdd } from '@coze-arch/bot-icons';
|
||||
import { isApiError } from '@coze-arch/bot-http';
|
||||
import {
|
||||
type InsertBotTableRequest,
|
||||
BotTableRWMode,
|
||||
type FieldItemType,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { SLSelect } from '../singleline-select';
|
||||
import { SLInput } from '../singleline-input';
|
||||
import {
|
||||
type TriggerType,
|
||||
type TableFieldsInfo,
|
||||
type TableBasicInfo,
|
||||
type ReadAndWriteModeOptions,
|
||||
type CreateType,
|
||||
type OnSave,
|
||||
} from '../../types';
|
||||
import {
|
||||
DATABASE_CONTENT_CHECK_ERROR_CODE,
|
||||
DATABASE_CONTENT_CHECK_ERROR_CODE_NEW,
|
||||
FIELD_TYPE_OPTIONS,
|
||||
RW_MODE_OPTIONS_CONFIG,
|
||||
RW_MODE_OPTIONS_MAP,
|
||||
SYSTEM_FIELDS,
|
||||
} from '../../const';
|
||||
import keyExample from '../../assets/key-example.png';
|
||||
import { ReactComponent as InfoSVG } from '../../assets/icon_info_outlined.svg';
|
||||
import { validateFields, validateNaming } from './helpers/validate';
|
||||
import { KeyTipsNode } from './components/KeyTipsNode';
|
||||
import { ColumnHeader } from './components/ColumnHeader';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const MIN_COL = 12;
|
||||
const MAX_COL = 24;
|
||||
const MAX_COLUMNS = 20;
|
||||
|
||||
export interface DatabaseTableStructureProps {
|
||||
data: DatabaseInfo;
|
||||
botId: string;
|
||||
forceEdit?: boolean;
|
||||
loading?: boolean;
|
||||
loadingTips?: string;
|
||||
/**
|
||||
* excel: 单用户模式|只读模式
|
||||
* normal: 单用户模式|只读模式
|
||||
* expert: 单用户模式|只读模式|多用户模式
|
||||
* undefined: 不支持读写模式
|
||||
*/
|
||||
readAndWriteModeOptions?: ReadAndWriteModeOptions;
|
||||
enableAdd?: boolean;
|
||||
maxColumnNum?: number;
|
||||
|
||||
useComputingEnableGoToNextStep?: (list: TableFieldsInfo) => void;
|
||||
onCancel?: () => void;
|
||||
onSave?: OnSave;
|
||||
onDeleteField?: (list: TableFieldsInfo) => void;
|
||||
setContentCheckErrorMsg?: (s: string) => void;
|
||||
|
||||
createType: CreateType;
|
||||
}
|
||||
|
||||
export interface DatabaseTableStructureRef {
|
||||
validate: () => Promise<boolean>;
|
||||
submit: () => Promise<void>;
|
||||
isReadonly: boolean;
|
||||
setTableFieldsList: (list: TableFieldsInfo) => void;
|
||||
tableFieldsList: TableFieldsInfo;
|
||||
tableBasicInfoFormRef: MutableRefObject<Form<TableBasicInfo>>;
|
||||
}
|
||||
|
||||
export const DatabaseTableStructure = forwardRef<
|
||||
DatabaseTableStructureRef,
|
||||
DatabaseTableStructureProps
|
||||
>((props, ref) => {
|
||||
const {
|
||||
data: initialData,
|
||||
botId,
|
||||
onSave,
|
||||
onCancel,
|
||||
onDeleteField,
|
||||
forceEdit = false,
|
||||
maxColumnNum = MAX_COLUMNS,
|
||||
useComputingEnableGoToNextStep,
|
||||
readAndWriteModeOptions,
|
||||
enableAdd = true,
|
||||
loading = false,
|
||||
setContentCheckErrorMsg = noop,
|
||||
loadingTips,
|
||||
createType,
|
||||
} = props;
|
||||
const getDefaultTableFieldsList = () => {
|
||||
if (initialData.readAndWriteMode === BotTableRWMode.UnlimitedReadWrite) {
|
||||
return [...SYSTEM_FIELDS, ...initialData.tableMemoryList];
|
||||
}
|
||||
return initialData.tableMemoryList;
|
||||
};
|
||||
|
||||
const [tableFieldsList, setTableFieldsList] = useState<TableFieldsInfo>(
|
||||
getDefaultTableFieldsList(),
|
||||
);
|
||||
|
||||
const inputRef = useRef<{
|
||||
triggerFocus?: () => void;
|
||||
}>(null);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const tableBasicInfoFormRef = useRef<Form<TableBasicInfo>>();
|
||||
|
||||
const isModify = Boolean(initialData.tableId);
|
||||
const [isReadonly, { setTrue: enableReadonly, setFalse: disableReadonly }] =
|
||||
useBoolean(false);
|
||||
const isRowMaxLimit = tableFieldsList.length >= maxColumnNum;
|
||||
const isExceedRowMaxLimit = tableFieldsList.length > maxColumnNum;
|
||||
const isEmptyList = !tableFieldsList
|
||||
.filter(i => !i.isSystemField)
|
||||
.filter(i => i.name || i.desc || i.type).length;
|
||||
const databaseAuditErrorCodes = [
|
||||
DATABASE_CONTENT_CHECK_ERROR_CODE,
|
||||
DATABASE_CONTENT_CHECK_ERROR_CODE_NEW,
|
||||
];
|
||||
const handleContentCheckError = (error: Error) => {
|
||||
if (
|
||||
isApiError(error) &&
|
||||
databaseAuditErrorCodes.includes(Number(error?.code))
|
||||
) {
|
||||
setContentCheckErrorMsg(
|
||||
error?.msg || I18n.t('knowledge_bot_update_databse_tnserr_msg'),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdd = (triggerFocus = true) => {
|
||||
if (isReadonly) {
|
||||
return;
|
||||
}
|
||||
const newTableFieldsList = [
|
||||
...tableFieldsList,
|
||||
{
|
||||
nanoid: nanoid(),
|
||||
name: '',
|
||||
desc: '',
|
||||
type: undefined as unknown as FieldItemType,
|
||||
must_required: false,
|
||||
},
|
||||
];
|
||||
|
||||
setTableFieldsList(newTableFieldsList);
|
||||
|
||||
if (triggerFocus) {
|
||||
setTimeout(() => {
|
||||
inputRef.current?.triggerFocus?.();
|
||||
scrollRef.current?.scrollIntoView({
|
||||
block: 'end',
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
const verifyTableFields = (trigger: TriggerType) => {
|
||||
setTableFieldsList(newTableFieldsList =>
|
||||
validateFields(newTableFieldsList, trigger),
|
||||
);
|
||||
};
|
||||
|
||||
const verifyAllBeforeSave = async (): Promise<boolean> => {
|
||||
// 触发 tableFields 校验
|
||||
const validatedTableFieldsList = validateFields(tableFieldsList, 'save');
|
||||
setTableFieldsList(validatedTableFieldsList);
|
||||
|
||||
// 触发并校验 tableBasicInfo
|
||||
try {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
await tableBasicInfoFormRef.current.formApi.validate(['name']);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 校验 tableFields
|
||||
if (
|
||||
validatedTableFieldsList.find(i =>
|
||||
Object.keys(i.errorMapper || {}).find(
|
||||
j => !!i?.errorMapper?.[j]?.length,
|
||||
),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 校验 tableFields 是否为空
|
||||
if (isEmptyList) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const tableBasicInfo = tableBasicInfoFormRef.current.formApi.getValues();
|
||||
|
||||
if (isModify) {
|
||||
sendTeaEvent(EVENT_NAMES.edit_table_click, {
|
||||
need_login: true,
|
||||
have_access: true,
|
||||
bot_id: botId,
|
||||
table_name: tableBasicInfo.name,
|
||||
});
|
||||
} else {
|
||||
sendTeaEvent(EVENT_NAMES.create_table_click, {
|
||||
need_login: true,
|
||||
have_access: true,
|
||||
bot_id: botId,
|
||||
table_name: tableBasicInfo.name,
|
||||
database_create_type: createType,
|
||||
});
|
||||
}
|
||||
|
||||
let resp;
|
||||
const params: InsertBotTableRequest['bot_table'] = {
|
||||
bot_id: botId,
|
||||
table_name: tableBasicInfo.name,
|
||||
table_desc: tableBasicInfo.desc || '',
|
||||
extra_info: {
|
||||
prompt_disabled: String(tableBasicInfo.prompt_disabled ? false : true),
|
||||
},
|
||||
field_list: tableFieldsList
|
||||
.filter(i => !!i.name && !i.isSystemField)
|
||||
.map(i => ({
|
||||
name: i.name,
|
||||
desc: i.desc || '',
|
||||
type: i.type,
|
||||
must_required: i.must_required,
|
||||
id: i.id,
|
||||
alterId: i.alterId,
|
||||
})),
|
||||
rw_mode: tableBasicInfo.readAndWriteMode,
|
||||
};
|
||||
|
||||
if (!isModify) {
|
||||
try {
|
||||
resp = await MemoryApi.InsertBotTable({
|
||||
bot_table: params,
|
||||
});
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseAddTable,
|
||||
error: error as Error,
|
||||
});
|
||||
handleContentCheckError(error as Error);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
resp = await MemoryApi.AlterBotTable({
|
||||
bot_table: {
|
||||
...params,
|
||||
id: initialData.tableId,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseAlterTable,
|
||||
error: error as Error,
|
||||
});
|
||||
handleContentCheckError(error as Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (onSave) {
|
||||
await onSave({
|
||||
response: resp,
|
||||
});
|
||||
}
|
||||
onCancel?.();
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
enableReadonly();
|
||||
|
||||
const validateRes = await verifyAllBeforeSave();
|
||||
if (!validateRes) {
|
||||
return;
|
||||
}
|
||||
|
||||
await save();
|
||||
} finally {
|
||||
disableReadonly();
|
||||
}
|
||||
};
|
||||
|
||||
const getTableNameErrorMessage = (v: string) => {
|
||||
if (!v) {
|
||||
return I18n.t('db_add_table_name_tips');
|
||||
}
|
||||
const errList = validateNaming(v);
|
||||
if (errList.length > 0) {
|
||||
return errList.join('; ');
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化 ref 属性
|
||||
useImperativeHandle<DatabaseTableStructureRef, DatabaseTableStructureRef>(
|
||||
ref,
|
||||
() => ({
|
||||
async submit() {
|
||||
return await handleSave();
|
||||
},
|
||||
async validate() {
|
||||
const res = await verifyAllBeforeSave();
|
||||
return res;
|
||||
},
|
||||
setTableFieldsList,
|
||||
isReadonly,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableBasicInfoFormRef,
|
||||
tableFieldsList,
|
||||
}),
|
||||
[isReadonly, tableFieldsList, tableBasicInfoFormRef],
|
||||
);
|
||||
|
||||
// 自定义表单项组件
|
||||
const FormInputInner: FC<any> = useCallback(
|
||||
p => {
|
||||
const { onChange, value, onBlur, validateStatus } = p;
|
||||
const errorMessage =
|
||||
validateStatus === 'error' ? getTableNameErrorMessage(value) : '';
|
||||
return (
|
||||
<SLInput
|
||||
value={value}
|
||||
handleChange={onChange}
|
||||
handleBlur={onBlur}
|
||||
inputProps={{
|
||||
'data-testid': BotE2e.BotDatabaseAddModalTableNameInput,
|
||||
disabled: !forceEdit && (isReadonly || isModify),
|
||||
placeholder: I18n.t('db_add_table_name_tips'),
|
||||
}}
|
||||
onFocusPopoverProps={{
|
||||
position: 'left',
|
||||
content: <KeyTipsNode />,
|
||||
}}
|
||||
errorMsgFloat
|
||||
errorMsg={errorMessage}
|
||||
className={classNames({
|
||||
[s['form-input-error']]: validateStatus === 'error',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[isReadonly, isModify, forceEdit],
|
||||
);
|
||||
const FormInput = useMemo(
|
||||
() =>
|
||||
withField(FormInputInner, {
|
||||
valueKey: 'value',
|
||||
onKeyChangeFnName: 'onChange',
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
// 校验是否 disable 下一步按钮
|
||||
useComputingEnableGoToNextStep?.(tableFieldsList);
|
||||
|
||||
const dataSource = enableAdd
|
||||
? [...tableFieldsList, { operate: 'add' }]
|
||||
: tableFieldsList;
|
||||
|
||||
const resetContentCheckErrorMsg = () => {
|
||||
setContentCheckErrorMsg('');
|
||||
};
|
||||
|
||||
return loading ? (
|
||||
<Spin
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
tip={loadingTips}
|
||||
wrapperClassName={s.spin}
|
||||
/>
|
||||
) : (
|
||||
<div className={s['table-structure-wrapper']}>
|
||||
<Form<TableBasicInfo>
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
ref={tableBasicInfoFormRef}
|
||||
layout="vertical"
|
||||
initValues={{
|
||||
name: initialData.name,
|
||||
desc: initialData.desc,
|
||||
prompt_disabled: isModify
|
||||
? initialData.extra_info?.prompt_disabled === 'true'
|
||||
? false
|
||||
: true
|
||||
: true,
|
||||
readAndWriteMode:
|
||||
initialData.readAndWriteMode || BotTableRWMode.LimitedReadWrite,
|
||||
}}
|
||||
className={s['table-structure-form']}
|
||||
onValueChange={(values, changedV) => {
|
||||
if ('name' in changedV || 'desc' in changedV) {
|
||||
resetContentCheckErrorMsg();
|
||||
}
|
||||
if (values.readAndWriteMode === BotTableRWMode.UnlimitedReadWrite) {
|
||||
setTableFieldsList(state => {
|
||||
if (state.some(i => i.isSystemField)) {
|
||||
return state;
|
||||
}
|
||||
return [...SYSTEM_FIELDS, ...state];
|
||||
});
|
||||
} else if (
|
||||
values.readAndWriteMode === BotTableRWMode.LimitedReadWrite
|
||||
) {
|
||||
setTableFieldsList(state => {
|
||||
if (state.some(i => i.isSystemField)) {
|
||||
return state.filter(i => !i.isSystemField);
|
||||
}
|
||||
return state;
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormInput
|
||||
field="name"
|
||||
label={{
|
||||
text: I18n.t('db_add_table_name'),
|
||||
required: true,
|
||||
}}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
validate={v => getTableNameErrorMessage(v)}
|
||||
noErrorMessage
|
||||
trigger={['change', 'blur']}
|
||||
fieldClassName={s['table-name-form-field']}
|
||||
/>
|
||||
<Form.TextArea
|
||||
data-testid={BotE2e.BotDatabaseAddModalTableDescInput}
|
||||
field="desc"
|
||||
label={I18n.t('db_add_table_desc')}
|
||||
disabled={isReadonly}
|
||||
rows={2}
|
||||
placeholder={I18n.t('db_add_table_desc_tips')}
|
||||
fieldClassName={s['table-desc-form-field']}
|
||||
/>
|
||||
<Row type="flex" justify="space-between">
|
||||
<Col span={12}>
|
||||
{readAndWriteModeOptions ? (
|
||||
<Form.Select
|
||||
data-testid={BotE2e.BotDatabaseAddModalTableQueryModeSelect}
|
||||
field="readAndWriteMode"
|
||||
style={{ width: '267px' }}
|
||||
label={{
|
||||
text: I18n.t('db_table_0129_001'),
|
||||
extra: (
|
||||
<Popover
|
||||
className={s.read_mode_popover}
|
||||
content={
|
||||
<div>
|
||||
<article>
|
||||
{RW_MODE_OPTIONS_MAP[readAndWriteModeOptions].map(
|
||||
i => (
|
||||
<p className={s['th-tip-dot']}>
|
||||
{RW_MODE_OPTIONS_CONFIG[i].tips}
|
||||
</p>
|
||||
),
|
||||
)}
|
||||
</article>
|
||||
</div>
|
||||
}
|
||||
position="top"
|
||||
showArrow
|
||||
>
|
||||
<Icon
|
||||
svg={<InfoSVG />}
|
||||
className={s['form-item-label-tooltip-icon']}
|
||||
/>
|
||||
</Popover>
|
||||
),
|
||||
}}
|
||||
dropdownClassName={s['table-setting-option']}
|
||||
optionList={RW_MODE_OPTIONS_MAP[readAndWriteModeOptions].map(
|
||||
i => ({
|
||||
label: RW_MODE_OPTIONS_CONFIG[i].label,
|
||||
value: i,
|
||||
}),
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
</Col>
|
||||
<Col span={readAndWriteModeOptions ? MIN_COL : MAX_COL}>
|
||||
<Form.Checkbox
|
||||
style={{
|
||||
width: 24,
|
||||
height: 24,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
className={s['form-item-checkbox']}
|
||||
labelPosition="left"
|
||||
field="prompt_disabled"
|
||||
label={{
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
fontWeight: 400,
|
||||
padding: '0 8px 0 0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: 'var(--Light-usage-text---color-text-0, #1D1C23)',
|
||||
},
|
||||
text: I18n.t('database_240618_01'),
|
||||
extra: (
|
||||
<Popover
|
||||
className={s.prompt_disabled_popover}
|
||||
content={I18n.t('database_240520_03')}
|
||||
position="top"
|
||||
showArrow
|
||||
>
|
||||
<Icon
|
||||
svg={<InfoSVG />}
|
||||
className={s['form-item-label-tooltip-icon']}
|
||||
/>
|
||||
</Popover>
|
||||
),
|
||||
}}
|
||||
></Form.Checkbox>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
|
||||
{isExceedRowMaxLimit ? (
|
||||
<Banner
|
||||
type="warning"
|
||||
description={I18n.t('db_table_0126_027', {
|
||||
ColumNum: maxColumnNum,
|
||||
})}
|
||||
className={s['max-row-banner']}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<UITable
|
||||
tableProps={{
|
||||
columns: [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: (
|
||||
<ColumnHeader
|
||||
label={I18n.t('db_add_table_field_name')}
|
||||
required
|
||||
tips={
|
||||
<div className={s['th-tip-name']}>
|
||||
<span style={{ width: 494, marginBottom: 8 }}>
|
||||
{I18n.t('db_add_table_field_name_tips')}
|
||||
</span>
|
||||
<Image
|
||||
preview={false}
|
||||
width={494}
|
||||
height={163}
|
||||
src={keyExample}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
),
|
||||
render: (text, record, index) =>
|
||||
record.operate !== 'add' ? (
|
||||
<SLInput
|
||||
style={{ position: 'static' }}
|
||||
onRef={inputRef}
|
||||
value={record.name}
|
||||
inputProps={{
|
||||
'data-testid': BotE2e.BotDatabaseAddModalFieldNameInput,
|
||||
'data-dtestid': BotE2e.BotDatabaseAddModalFieldNameInput,
|
||||
disabled: isReadonly || record.isSystemField,
|
||||
placeholder: 'Enter Name',
|
||||
}}
|
||||
errorMsgFloat
|
||||
onFocusPopoverProps={{
|
||||
position: 'left',
|
||||
content: <KeyTipsNode />,
|
||||
}}
|
||||
errorMsg={tableFieldsList[index]?.errorMapper?.name?.join(
|
||||
'; ',
|
||||
)}
|
||||
handleChange={v => {
|
||||
const newTableMemoryList = [...tableFieldsList];
|
||||
newTableMemoryList[index].name = v;
|
||||
setTableFieldsList(newTableMemoryList);
|
||||
verifyTableFields('change');
|
||||
resetContentCheckErrorMsg();
|
||||
}}
|
||||
handleBlur={() => {
|
||||
verifyTableFields('blur');
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
ref={scrollRef}
|
||||
data-testid={BotE2e.BotDatabaseAddModalAddBtn}
|
||||
>
|
||||
{isRowMaxLimit ? (
|
||||
<div style={{ paddingRight: 10 }}>
|
||||
<Tooltip
|
||||
position="top"
|
||||
content={I18n.t('bot_database_add_field', {
|
||||
number: maxColumnNum,
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
theme="light"
|
||||
style={{ width: 240 }}
|
||||
type="tertiary"
|
||||
disabled
|
||||
icon={<IconAdd />}
|
||||
>
|
||||
{I18n.t('bot_userProfile_add')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
style={{ width: 240 }}
|
||||
theme="light"
|
||||
type="tertiary"
|
||||
disabled={isReadonly}
|
||||
onClick={() => handleAdd(true)}
|
||||
icon={<IconAdd />}
|
||||
>
|
||||
{I18n.t('bot_userProfile_add')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
width: 246,
|
||||
},
|
||||
{
|
||||
dataIndex: 'desc',
|
||||
title: (
|
||||
<ColumnHeader
|
||||
label={I18n.t('db_add_table_field_desc')}
|
||||
required={false}
|
||||
tips={
|
||||
<article style={{ width: 327 }}>
|
||||
{I18n.t('db_add_table_field_desc_tips')}
|
||||
</article>
|
||||
}
|
||||
/>
|
||||
),
|
||||
render: (text, record, index) =>
|
||||
record.operate !== 'add' ? (
|
||||
<SLInput
|
||||
value={record.desc}
|
||||
maxCount={300}
|
||||
inputProps={{
|
||||
'data-testid': `${BotE2e.BotDatabaseAddModalFieldDescInput}.${index}.${record.name}`,
|
||||
'data-dtestid': `${BotE2e.BotDatabaseAddModalFieldDescInput}.${index}.${record.name}`,
|
||||
maxLength: 300,
|
||||
disabled: isReadonly || record.isSystemField,
|
||||
placeholder: I18n.t(
|
||||
'bot_edit_variable_description_placeholder',
|
||||
),
|
||||
}}
|
||||
errorMsgFloat
|
||||
handleChange={v => {
|
||||
const newTableMemoryList = [...tableFieldsList];
|
||||
newTableMemoryList[index].desc = v;
|
||||
setTableFieldsList(newTableMemoryList);
|
||||
resetContentCheckErrorMsg();
|
||||
}}
|
||||
/>
|
||||
) : null,
|
||||
width: 376,
|
||||
},
|
||||
{
|
||||
dataIndex: 'type',
|
||||
title: (
|
||||
<ColumnHeader
|
||||
label={I18n.t('db_add_table_field_type')}
|
||||
required={true}
|
||||
tips={
|
||||
<article style={{ width: 327 }}>
|
||||
{I18n.t('db_add_table_field_type_tips')}
|
||||
</article>
|
||||
}
|
||||
/>
|
||||
),
|
||||
render: (text, record, index) =>
|
||||
record.operate !== 'add' ? (
|
||||
<SLSelect
|
||||
value={record.type}
|
||||
selectProps={{
|
||||
'data-testid': `${BotE2e.BotDatabaseAddModalFieldTypeSelect}.${index}.${record.name}`,
|
||||
'data-dtestid': `${BotE2e.BotDatabaseAddModalFieldTypeSelect}.${index}.${record.name}`,
|
||||
disabled:
|
||||
isReadonly ||
|
||||
(isModify && !!record.id) ||
|
||||
record.isSystemField,
|
||||
placeholder: I18n.t('db_table_save_exception_fieldtype'),
|
||||
optionList: FIELD_TYPE_OPTIONS,
|
||||
}}
|
||||
errorMsgFloat
|
||||
errorMsg={tableFieldsList[index]?.errorMapper?.type?.join(
|
||||
'; ',
|
||||
)}
|
||||
handleChange={v => {
|
||||
const newTableMemoryList = [...tableFieldsList];
|
||||
newTableMemoryList[index].type = v as FieldItemType;
|
||||
setTableFieldsList(newTableMemoryList);
|
||||
verifyTableFields('change');
|
||||
}}
|
||||
/>
|
||||
) : null,
|
||||
width: 211,
|
||||
},
|
||||
{
|
||||
dataIndex: 'must_required',
|
||||
title: (
|
||||
<ColumnHeader
|
||||
label={I18n.t('db_add_table_field_necessary')}
|
||||
required={false}
|
||||
tips={
|
||||
<article style={{ width: 327 }}>
|
||||
<p className={s['th-tip-dot']}>
|
||||
{I18n.t('db_add_table_field_necessary_tips1')}
|
||||
</p>
|
||||
<p className={s['th-tip-dot']}>
|
||||
{I18n.t('db_add_table_field_necessary_tips2')}
|
||||
</p>
|
||||
</article>
|
||||
}
|
||||
/>
|
||||
),
|
||||
render: (text, record, index) =>
|
||||
record.operate !== 'add' ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Switch
|
||||
data-testid={`${BotE2e.BotDatabaseAddModalFieldRequiredSwitch}.${index}.${record.name}`}
|
||||
data-dtestid={`${BotE2e.BotDatabaseAddModalFieldRequiredSwitch}.${index}.${record.name}`}
|
||||
style={{ margin: '4px 0 4px 12px' }}
|
||||
disabled={
|
||||
isReadonly ||
|
||||
record.disableMustRequired ||
|
||||
record.isSystemField
|
||||
}
|
||||
checked={record.must_required}
|
||||
onChange={v => {
|
||||
const newTableMemoryList = [...tableFieldsList];
|
||||
newTableMemoryList[index].must_required = v;
|
||||
setTableFieldsList(newTableMemoryList);
|
||||
}}
|
||||
aria-label="a switch for semi demo"
|
||||
/>
|
||||
</div>
|
||||
) : null,
|
||||
width: 108,
|
||||
},
|
||||
{
|
||||
dataIndex: 'operate',
|
||||
title: I18n.t('db_table_0126_021'),
|
||||
render: (text, record, index) =>
|
||||
record.operate !== 'add' ? (
|
||||
<UITableAction
|
||||
deleteProps={{
|
||||
handleClick: () => {
|
||||
if (isReadonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newTableMemoryList = [
|
||||
...tableFieldsList.slice(0, index),
|
||||
...tableFieldsList.slice(index + 1),
|
||||
];
|
||||
|
||||
setTableFieldsList(newTableMemoryList);
|
||||
verifyTableFields('change');
|
||||
onDeleteField?.(newTableMemoryList);
|
||||
},
|
||||
popconfirm: {
|
||||
defaultVisible: false,
|
||||
visible: false,
|
||||
},
|
||||
tooltip: {
|
||||
content: I18n.t('datasets_table_title_actions_delete'),
|
||||
},
|
||||
disabled: record.isSystemField,
|
||||
}}
|
||||
editProps={{
|
||||
hide: true,
|
||||
}}
|
||||
/>
|
||||
) : null,
|
||||
width: 85,
|
||||
},
|
||||
],
|
||||
dataSource,
|
||||
pagination: false,
|
||||
className: s['table-structure-table'],
|
||||
rowKey: 'nanoid',
|
||||
}}
|
||||
wrapperClassName={s['table-structure-table-wrapper']}
|
||||
/>
|
||||
{isEmptyList ? (
|
||||
<div className={s['table-empty-tips']}>
|
||||
{I18n.t('db_table_save_exception_nofield')}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
// semi 原有的 disable 样式不满足需求,需要更明显的文字颜色
|
||||
:global {
|
||||
.semi-input-wrapper-disabled {
|
||||
-webkit-text-fill-color: rgba(29, 28, 35, 100%);
|
||||
}
|
||||
|
||||
.semi-input-suffix {
|
||||
-webkit-text-fill-color: var(--semi-color-disabled-text, rgba(56, 55, 67, 20%))
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.error-wrapper {
|
||||
:global {
|
||||
.semi-input-wrapper {
|
||||
border: 1px solid @error-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-content {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.error-float {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
position: absolute;
|
||||
padding-top: 2px;
|
||||
font-size: 12px;
|
||||
color: @error-red;
|
||||
}
|
||||
|
||||
.input {
|
||||
input {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.limit-count {
|
||||
overflow: hidden;
|
||||
|
||||
padding-right: 12px;
|
||||
padding-left: 8px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-3, rgba(28, 31, 35, 35%));
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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 ComponentProps,
|
||||
useRef,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useEffect,
|
||||
type ForwardedRef,
|
||||
} from 'react';
|
||||
|
||||
import isNumber from 'lodash-es/isNumber';
|
||||
import cs from 'classnames';
|
||||
import { useReactive } from 'ahooks';
|
||||
import { type TooltipProps } from '@coze-arch/bot-semi/Tooltip';
|
||||
import { type PopoverProps } from '@coze-arch/bot-semi/Popover';
|
||||
import { type InputProps } from '@coze-arch/bot-semi/Input';
|
||||
import { Input, Popover, Tooltip } from '@coze-arch/bot-semi';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface SLInputRefType {
|
||||
triggerFocus?: () => void;
|
||||
}
|
||||
|
||||
export type SLInputProps = ComponentProps<typeof Input> & {
|
||||
value: string | undefined;
|
||||
onRef?: ForwardedRef<SLInputRefType>;
|
||||
ellipsis?: boolean;
|
||||
handleChange?: (v: string) => void;
|
||||
handleBlur?: (v: string) => void;
|
||||
handleFocus?: (v: string) => void;
|
||||
ellipsisPopoverProps?: PopoverProps;
|
||||
onFocusPopoverProps?: PopoverProps;
|
||||
tooltipProps?: TooltipProps;
|
||||
inputProps?: InputProps & { 'data-dtestid'?: string; 'data-testid'?: string };
|
||||
errorMsg?: string;
|
||||
errorMsgFloat?: boolean;
|
||||
maxCount?: number;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export const SLInput: React.FC<SLInputProps> = props => {
|
||||
const { ellipsis = true, maxCount, errorMsgFloat } = props;
|
||||
const showCount = isNumber(maxCount) && maxCount > 0;
|
||||
useImperativeHandle(props.onRef, () => ({
|
||||
triggerFocus,
|
||||
}));
|
||||
const $state = useReactive({
|
||||
value: props.value,
|
||||
inputOnFocus: false,
|
||||
inputEle: false,
|
||||
});
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const triggerFocus = () => {
|
||||
$state.inputEle = true;
|
||||
inputRef?.current?.focus();
|
||||
};
|
||||
|
||||
const onFocus = () => {
|
||||
$state.inputOnFocus = true;
|
||||
$state.inputEle = true;
|
||||
props?.handleFocus?.($state.value || '');
|
||||
};
|
||||
|
||||
const onBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
$state.inputOnFocus = false;
|
||||
props?.handleBlur?.($state.value || '');
|
||||
props?.onBlur?.(e);
|
||||
$state.inputEle = false;
|
||||
};
|
||||
|
||||
const onChange = (v: string) => {
|
||||
$state.value = v;
|
||||
props?.handleChange?.(v);
|
||||
};
|
||||
|
||||
const onclick = () => {
|
||||
if (!$state.inputEle) {
|
||||
setTimeout(() => {
|
||||
inputRef?.current?.focus();
|
||||
}, 10);
|
||||
}
|
||||
$state.inputEle = true;
|
||||
};
|
||||
const hasEllipsis = useMemo(() => {
|
||||
const clientWidth = inputRef.current?.clientWidth || 0;
|
||||
const scrollWidth = inputRef.current?.scrollWidth || 0;
|
||||
return clientWidth < scrollWidth - 1;
|
||||
}, [
|
||||
ellipsis,
|
||||
$state.inputOnFocus,
|
||||
$state.value,
|
||||
inputRef.current?.clientWidth,
|
||||
inputRef.current?.scrollWidth,
|
||||
$state.inputEle,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
$state.value = props.value;
|
||||
}, [props.value]);
|
||||
|
||||
const LimitCountNode = (
|
||||
<span className={s['limit-count']}>
|
||||
{$state.value?.length || 0}/{maxCount}
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cs(s['input-wrapper'], props.className)}
|
||||
style={props.style}
|
||||
>
|
||||
{!$state.inputEle && hasEllipsis ? (
|
||||
<Tooltip
|
||||
content={
|
||||
<article
|
||||
style={{
|
||||
maxWidth: 200,
|
||||
wordWrap: 'break-word',
|
||||
wordBreak: 'normal',
|
||||
}}
|
||||
>
|
||||
{$state.value}
|
||||
</article>
|
||||
}
|
||||
position={'top'}
|
||||
showArrow
|
||||
mouseEnterDelay={300}
|
||||
{...props.tooltipProps}
|
||||
>
|
||||
<div
|
||||
className={cs(props?.errorMsg ? s['error-wrapper'] : null)}
|
||||
onClick={onclick}
|
||||
>
|
||||
<Input
|
||||
{...props.inputProps}
|
||||
ref={inputRef}
|
||||
value={$state.value}
|
||||
className={ellipsis ? s.input : ''}
|
||||
suffix={showCount ? LimitCountNode : undefined}
|
||||
></Input>
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<div className={cs(props?.errorMsg ? s['error-wrapper'] : null)}>
|
||||
<Popover
|
||||
{...props.onFocusPopoverProps}
|
||||
trigger="custom"
|
||||
visible={
|
||||
Boolean(props.onFocusPopoverProps?.content) && $state.inputOnFocus
|
||||
}
|
||||
showArrow
|
||||
>
|
||||
<Input
|
||||
{...props.inputProps}
|
||||
ref={inputRef}
|
||||
value={$state.value}
|
||||
className={ellipsis ? s.input : ''}
|
||||
onChange={onChange}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
suffix={showCount ? LimitCountNode : undefined}
|
||||
></Input>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
{props?.errorMsg ? (
|
||||
<div
|
||||
className={cs({
|
||||
[s['error-content']]: true,
|
||||
[s['error-float']]: Boolean(errorMsgFloat),
|
||||
})}
|
||||
>
|
||||
<div className={s['error-text']}>{props?.errorMsg}</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
.select-wrapper {
|
||||
width: 100%;
|
||||
|
||||
// semi 原有的 disable 样式不满足需求,需要更明显的文字颜色
|
||||
:global {
|
||||
.semi-select-disabled .semi-select-selection {
|
||||
color: rgba(29, 28, 35, 100%);
|
||||
}
|
||||
|
||||
.semi-select-disabled .semi-select-arrow {
|
||||
color: rgba(29, 28, 35, 60%);
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.error-wrapper {
|
||||
:global {
|
||||
.semi-select {
|
||||
border: 1px solid @error-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-content {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.error-float {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
position: absolute;
|
||||
padding-top: 2px;
|
||||
font-size: 12px;
|
||||
color: @error-red;
|
||||
}
|
||||
|
||||
|
||||
.selected-option {
|
||||
:global {
|
||||
.semi-select-option-selected {
|
||||
.semi-select-option-icon {
|
||||
color: #4D53E8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classnames from 'classnames';
|
||||
import { type SelectProps } from '@coze-arch/bot-semi/Select';
|
||||
import { type InputProps } from '@coze-arch/bot-semi/Input';
|
||||
import { Select } from '@coze-arch/bot-semi';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface SLSelectRefType {
|
||||
triggerFocus?: () => void;
|
||||
}
|
||||
|
||||
export type SLSelectProps = InputProps & {
|
||||
value: SelectProps['value'];
|
||||
handleChange?: (v: SelectProps['value']) => void;
|
||||
errorMsg?: string;
|
||||
errorMsgFloat?: boolean;
|
||||
selectProps?: SelectProps & {
|
||||
'data-testid'?: string;
|
||||
'data-dtestid'?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const SLSelect: React.FC<SLSelectProps> = props => {
|
||||
const { errorMsg, errorMsgFloat } = props;
|
||||
return (
|
||||
<div
|
||||
className={classnames({
|
||||
[s['select-wrapper']]: true,
|
||||
[s['error-wrapper']]: Boolean(errorMsg),
|
||||
})}
|
||||
>
|
||||
<Select
|
||||
{...props.selectProps}
|
||||
style={{ width: '100%' }}
|
||||
value={props.value}
|
||||
onChange={v => {
|
||||
props?.handleChange?.(v);
|
||||
}}
|
||||
dropdownClassName={s['selected-option']}
|
||||
/>
|
||||
{errorMsg ? (
|
||||
<div
|
||||
className={classnames({
|
||||
[s['error-content']]: true,
|
||||
[s['error-float']]: Boolean(errorMsgFloat),
|
||||
})}
|
||||
>
|
||||
<div className={s['error-text']}>{errorMsg}</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
132
frontend/packages/data/memory/database-creator/src/const.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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 { nanoid } from 'nanoid';
|
||||
import {
|
||||
type DatabaseInfo,
|
||||
type TableMemoryItem,
|
||||
} from '@coze-studio/bot-detail-store';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { FieldItemType, BotTableRWMode } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import { type ReadAndWriteModeOptions } from './types';
|
||||
|
||||
export const FIELD_TYPE_OPTIONS = [
|
||||
{ value: FieldItemType.Text, label: I18n.t('db_add_table_field_type_txt') },
|
||||
{ value: FieldItemType.Number, label: I18n.t('db_add_table_field_type_int') },
|
||||
{ value: FieldItemType.Date, label: I18n.t('db_add_table_field_type_time') },
|
||||
{
|
||||
value: FieldItemType.Float,
|
||||
label: I18n.t('db_add_table_field_type_number'),
|
||||
},
|
||||
{
|
||||
value: FieldItemType.Boolean,
|
||||
label: I18n.t('db_add_table_field_type_bool'),
|
||||
},
|
||||
];
|
||||
|
||||
export const TEMPLATE_INFO: DatabaseInfo = {
|
||||
name: 'book_notes',
|
||||
desc: I18n.t('db_add_table_temp_desc'),
|
||||
tableId: '',
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: [
|
||||
{
|
||||
nanoid: nanoid(),
|
||||
name: 'name',
|
||||
desc: I18n.t('db_add_table_temp_field_desc1'),
|
||||
type: FieldItemType.Text,
|
||||
must_required: true,
|
||||
},
|
||||
{
|
||||
name: 'section',
|
||||
nanoid: nanoid(),
|
||||
desc: I18n.t('db_add_table_temp_field_desc2'),
|
||||
type: FieldItemType.Number,
|
||||
must_required: false,
|
||||
},
|
||||
{
|
||||
name: 'note',
|
||||
nanoid: nanoid(),
|
||||
desc: I18n.t('db_add_table_temp_field_desc3'),
|
||||
type: FieldItemType.Text,
|
||||
must_required: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const RW_MODE_OPTIONS_CONFIG: Record<
|
||||
BotTableRWMode,
|
||||
{ tips: string; label: string }
|
||||
> = {
|
||||
[BotTableRWMode.LimitedReadWrite]: {
|
||||
tips: I18n.t('db_table_0129_005'),
|
||||
label: I18n.t('db_table_0129_002'),
|
||||
},
|
||||
[BotTableRWMode.ReadOnly]: {
|
||||
tips: I18n.t('db_table_0129_006'),
|
||||
label: I18n.t('db_table_0129_003'),
|
||||
},
|
||||
[BotTableRWMode.UnlimitedReadWrite]: {
|
||||
tips: I18n.t('db_table_0129_007'),
|
||||
label: I18n.t('db_table_0129_004'),
|
||||
},
|
||||
[BotTableRWMode.RWModeMax]: {
|
||||
tips: '',
|
||||
label: '',
|
||||
},
|
||||
};
|
||||
|
||||
export const RW_MODE_OPTIONS_MAP: Record<
|
||||
ReadAndWriteModeOptions,
|
||||
BotTableRWMode[]
|
||||
> = {
|
||||
excel: [BotTableRWMode.LimitedReadWrite],
|
||||
normal: [BotTableRWMode.LimitedReadWrite],
|
||||
expert: [BotTableRWMode.LimitedReadWrite, BotTableRWMode.UnlimitedReadWrite],
|
||||
};
|
||||
|
||||
export const DATABASE_CONTENT_CHECK_ERROR_CODE = 708024072;
|
||||
export const DATABASE_CONTENT_CHECK_ERROR_CODE_NEW = 708334072;
|
||||
|
||||
/**
|
||||
* 内置字段: uuid
|
||||
*/
|
||||
export const USER_ID_FIELD: TableMemoryItem = {
|
||||
name: 'uuid',
|
||||
desc: I18n.t('workflow_240221_01'),
|
||||
type: FieldItemType.Text,
|
||||
must_required: true,
|
||||
nanoid: nanoid(),
|
||||
isSystemField: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* 内置字段: id
|
||||
*/
|
||||
export const ID_FIELD: TableMemoryItem = {
|
||||
name: 'id',
|
||||
desc: I18n.t('database_240520_01'),
|
||||
type: FieldItemType.Number,
|
||||
must_required: true,
|
||||
nanoid: nanoid(),
|
||||
isSystemField: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* 内置系统字段
|
||||
*/
|
||||
export const SYSTEM_FIELDS = [USER_ID_FIELD, ID_FIELD];
|
||||
17
frontend/packages/data/memory/database-creator/src/global.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/// <reference types='@coze-arch/bot-typings' />
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 useCreateFromExcelFG = (): boolean =>
|
||||
// Feature flag bot.data.excel_to_database is OFF, returning false.
|
||||
false;
|
||||
21
frontend/packages/data/memory/database-creator/src/index.tsx
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.
|
||||
*/
|
||||
|
||||
export { DatabaseModal, DatabaseModalProps } from './components/database-modal';
|
||||
export {
|
||||
ProcessingTag,
|
||||
ProcessingTagProps,
|
||||
} from './components/database-create-from-excel/components/processing-tag';
|
||||
66
frontend/packages/data/memory/database-creator/src/types.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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 {
|
||||
type AlterBotTableResponse,
|
||||
type InsertBotTableResponse,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
|
||||
export type OnSave = (params: {
|
||||
response: InsertBotTableResponse | AlterBotTableResponse;
|
||||
}) => Promise<void>;
|
||||
|
||||
export enum CreateType {
|
||||
custom = 'custom',
|
||||
template = 'template',
|
||||
excel = 'excel',
|
||||
// 推荐建表
|
||||
recommend = 'recommend',
|
||||
// 输入自然语言建表
|
||||
naturalLanguage = 'naturalLanguage',
|
||||
}
|
||||
|
||||
export interface MapperItem {
|
||||
label: string;
|
||||
key: string;
|
||||
validator: {
|
||||
type: VerifyType;
|
||||
message: string;
|
||||
}[];
|
||||
defaultValue: any;
|
||||
require: boolean;
|
||||
}
|
||||
|
||||
export type TableBasicInfo = Pick<
|
||||
DatabaseInfo,
|
||||
'name' | 'desc' | 'readAndWriteMode'
|
||||
> & { prompt_disabled: boolean };
|
||||
export type TableFieldsInfo = DatabaseInfo['tableMemoryList'];
|
||||
|
||||
export enum VerifyType {
|
||||
Required = 1,
|
||||
Unique = 2,
|
||||
Naming = 3,
|
||||
}
|
||||
|
||||
export type TriggerType = 'blur' | 'change' | 'save';
|
||||
|
||||
export interface NL2DBInfo {
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
export type ReadAndWriteModeOptions = 'excel' | 'normal' | 'expert';
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"$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-flags/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-http/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/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": "../../../../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": "../../../studio/components/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../studio/stores/bot-detail/tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
frontend/packages/data/memory/database-creator/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": ["**/*"]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"include": ["__tests__", "stories", "vitest.config.ts"],
|
||||
"exclude": ["./dist"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"rootDir": "./",
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/globals"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import svgr from 'vite-plugin-svgr';
|
||||
import { defineConfig } from '@coze-arch/vitest-config';
|
||||
|
||||
export default defineConfig(
|
||||
{
|
||||
plugins: [svgr()],
|
||||
dirname: __dirname,
|
||||
preset: 'web',
|
||||
test: {
|
||||
coverage: {
|
||||
all: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
fixSemi: true,
|
||||
},
|
||||
);
|
||||