chore: import upstream snapshot with attribution
@@ -0,0 +1,5 @@
|
||||
const { defineConfig } = require('@coze-arch/stylelint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
extends: [],
|
||||
});
|
||||
@@ -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[]) => {
|
||||
// In fact, it is all false. Suppose the first argument of args is field and the second argument is a specific 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,29 @@
|
||||
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,
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['src/**/namespaces/*.ts'],
|
||||
rules: {
|
||||
'unicorn/filename-case': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
// If it returns false, it will be returned directly.
|
||||
if (typeof res === 'boolean' && res === false) {
|
||||
setSubmitButtonLoading(false);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
setSubmitButtonLoading(false);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// onSubmit
|
||||
try {
|
||||
// Determine if the passed submit function is asynchronous, then the button loading
|
||||
const callbackResult = onSubmit?.();
|
||||
if (callbackResult instanceof Promise) {
|
||||
setSubmitButtonLoading(true);
|
||||
}
|
||||
await callbackResult;
|
||||
|
||||
if (isLastStep) {
|
||||
//close
|
||||
onCancel?.();
|
||||
} else {
|
||||
// Next step
|
||||
useStepStore.setState(state => ({
|
||||
step: Math.min(state.step + 1, lastStep),
|
||||
}));
|
||||
}
|
||||
} finally {
|
||||
setSubmitButtonLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickPrev = () => {
|
||||
getCallbacks()?.onPrevious?.();
|
||||
if (isFirstStep) {
|
||||
// close
|
||||
onCancel?.();
|
||||
} else {
|
||||
// previous step
|
||||
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: This demand is suspended, the backend is offline, and it will be opened later.
|
||||
// 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: This demand is suspended, the backend is offline, and it will be opened later.
|
||||
// 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,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Previous Save the current state
|
||||
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: There is a bug in semi here, and it is always override update, so you need to add the sheetID attribute as well.
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
excelInfoFormRef.current.formApi.setValues({
|
||||
sheetID: changedValue.sheetID,
|
||||
headerRow: 1,
|
||||
dataStartRow: 2,
|
||||
});
|
||||
}
|
||||
|
||||
// Switch sheet
|
||||
if (
|
||||
changedKeys.length === 3 &&
|
||||
changedKeys.includes('headerRow') &&
|
||||
changedKeys.includes('dataStartRow') &&
|
||||
changedKeys.includes('sheetID')
|
||||
) {
|
||||
await reloadTableValue({ updateTableName: true });
|
||||
}
|
||||
|
||||
// Update headerRow only
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// Update dataStartRow only
|
||||
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 {
|
||||
// business
|
||||
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 configuration
|
||||
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 hiding is taken here to keep the upload process, otherwise the upload will be cancelled
|
||||
*/
|
||||
...(fileList.length > 0
|
||||
? {
|
||||
display: 'none',
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
onAcceptInvalid={() => {
|
||||
Toast.warning({
|
||||
showClose: false,
|
||||
content: I18n.t('db_table_0126_032'),
|
||||
});
|
||||
}}
|
||||
beforeUpload={fileInfo => {
|
||||
// The reason for not limiting by the maxSize property is
|
||||
// Only the beforeUpload hook can change 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 }) => {
|
||||
// It will only be uploaded if the verification is passed.
|
||||
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 limit
|
||||
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] Import file data pre-check
|
||||
*/
|
||||
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] Import file data task information query
|
||||
*/
|
||||
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] Import file Add 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,119 @@
|
||||
// eslint-disable unicorn/filename-case
|
||||
/*
|
||||
* 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 {
|
||||
/** single user mode */
|
||||
LimitedReadWrite = 1,
|
||||
/** read-only mode */
|
||||
ReadOnly = 2,
|
||||
/** multi-user mode */
|
||||
UnlimitedReadWrite = 3,
|
||||
/** Max boundary value */
|
||||
RWModeMax = 4,
|
||||
}
|
||||
|
||||
export enum BotTableStatus {
|
||||
/** Initialization (not available) */
|
||||
Init = 0,
|
||||
/** It's online now. */
|
||||
Online = 1,
|
||||
/** delete */
|
||||
Delete = 2,
|
||||
/** Draft status (not published) */
|
||||
Draft = 3,
|
||||
}
|
||||
|
||||
export enum FieldItemType {
|
||||
/** Text */
|
||||
Text = 1,
|
||||
/** number */
|
||||
Number = 2,
|
||||
/** time */
|
||||
Date = 3,
|
||||
/** float */
|
||||
Float = 4,
|
||||
/** bool */
|
||||
Boolean = 5,
|
||||
}
|
||||
|
||||
/** Table model related constants, structure definitions */
|
||||
export enum FieldType {
|
||||
/** Text */
|
||||
Text = 1,
|
||||
/** number */
|
||||
Number = 2,
|
||||
/** time */
|
||||
Date = 3,
|
||||
/** float */
|
||||
Float = 4,
|
||||
/** bool */
|
||||
Boolean = 5,
|
||||
}
|
||||
|
||||
export enum ImportFileTaskStatus {
|
||||
/** task initialization */
|
||||
Init = 1,
|
||||
/** Task in progress */
|
||||
Enqueue = 2,
|
||||
/** Mission successful */
|
||||
Succeed = 3,
|
||||
/** Mission failed */
|
||||
Failed = 4,
|
||||
}
|
||||
|
||||
export enum Language {
|
||||
/** Chinese */
|
||||
Chinese = 1,
|
||||
/** English */
|
||||
English = 2,
|
||||
}
|
||||
|
||||
export enum TableType {
|
||||
/** draft */
|
||||
DraftTable = 1,
|
||||
/** online */
|
||||
OnlineTable = 2,
|
||||
}
|
||||
|
||||
export interface FieldItem {
|
||||
/** Field name, user-defined, possibly in Chinese */
|
||||
name: string;
|
||||
desc?: string;
|
||||
type: FieldItemType;
|
||||
must_required?: boolean;
|
||||
/** Field Id, server level generated, globally unique (added as 0) */
|
||||
id?: Int64;
|
||||
/** Field Name Language Type */
|
||||
lang?: Language;
|
||||
/** Physical field name, server level generation, unique under a single table */
|
||||
physics_name?: string;
|
||||
/** Whether primary key */
|
||||
primary_key?: boolean;
|
||||
/** Field visibility, 1: user-defined; 2: business definition, visible to users; 3: business definition, hidden from users */
|
||||
visibility?: number;
|
||||
/** Used in an excel document, map to the corresponding column in excel */
|
||||
sequence?: string;
|
||||
/** Business custom extension field metadata */
|
||||
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 {
|
||||
/** Related id bot_id */
|
||||
bot_id: string;
|
||||
/** table_id */
|
||||
table_id: string;
|
||||
/** table name */
|
||||
table_name: string;
|
||||
/** Upload the task id of TableFile for later query use.
|
||||
DataModelService uses GID to ensure that task_id globally unique, and subsequent queries only require task_id.
|
||||
The DataModel service will record the lastTaskID of the table, which can be found by table_id when querying
|
||||
task_id */
|
||||
task_id: Int64;
|
||||
code: number;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
export interface FileInfo {
|
||||
/** tos uri */
|
||||
tos_uri: string;
|
||||
/** Excel line number */
|
||||
header_row: Int64;
|
||||
/** Excel data start line */
|
||||
start_data_row: Int64;
|
||||
/** Excel sheet id, 0 for default */
|
||||
sheet_id?: number;
|
||||
}
|
||||
|
||||
export interface ImportTableInfo {
|
||||
/** Table belongs to bot_id */
|
||||
bot_id: string;
|
||||
/** table name */
|
||||
table_name: string;
|
||||
/** table description */
|
||||
table_desc?: string;
|
||||
/** Field information */
|
||||
table_meta: Array<table_base.FieldItem>;
|
||||
/** Space 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: This demand is suspended, the backend is offline, and it will be opened later.
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Stop polling without Processing
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
if (res?.status !== ImportFileTaskStatus.Enqueue) {
|
||||
clearInterval(importResultRef.current);
|
||||
importResultRef.current = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* After success --- write to localStorage
|
||||
* No task --- write to 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(() => {
|
||||
// Read localStorage to reduce unnecessary requests
|
||||
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>;
|
||||
}
|
||||
|
||||
// Used to store static state, in non-initialization scenarios, read-only and not modifiable
|
||||
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: The meaning of this property name is unclear. For compatibility, this property name will not be modified for the time being, but a more explicit naming needs to be used in the future.
|
||||
'use-local-storage-state-modify-tips',
|
||||
{
|
||||
defaultValue: '',
|
||||
},
|
||||
);
|
||||
|
||||
const language = getUnReactiveLanguage();
|
||||
const isEdit = Boolean(data.tableId);
|
||||
const [isReadonly, setIsReadonly] = useState(false);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setIsReadonly(true);
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
await tableStructureRef.current.submit();
|
||||
} finally {
|
||||
setIsReadonly(false);
|
||||
}
|
||||
};
|
||||
|
||||
const hideTableStructureTips = useMemo(() => {
|
||||
const lsMap = JSON.parse(
|
||||
mapOfShouldHidingDatabaseTableStructureTips || '{}',
|
||||
);
|
||||
|
||||
return (
|
||||
!isEdit ||
|
||||
lsMap?.[botId] ||
|
||||
shouldHideDatabaseTableStructureTipsForCurrent
|
||||
);
|
||||
}, [
|
||||
isEdit,
|
||||
shouldHideDatabaseTableStructureTipsForCurrent,
|
||||
mapOfShouldHidingDatabaseTableStructureTips,
|
||||
]);
|
||||
|
||||
const title = useMemo(() => {
|
||||
if (isEdit) {
|
||||
return I18n.t('db_edit_title');
|
||||
}
|
||||
if (createType === CreateType.excel) {
|
||||
return I18n.t('db_table_0126_011');
|
||||
}
|
||||
return I18n.t('db_add_table_title');
|
||||
}, [isEdit, createType]);
|
||||
|
||||
const showEntry = isEntry && !isEdit && !NL2DBInfo;
|
||||
const shouldShowAIGenerate =
|
||||
/**
|
||||
* 1. The entrance is not displayed
|
||||
* 2. Editing status is not displayed
|
||||
* 3. Excel is not displayed when importing
|
||||
*/
|
||||
!showEntry && !isEdit && createType !== CreateType.excel;
|
||||
|
||||
const setDataToDefault = () => {
|
||||
setData({
|
||||
name: '',
|
||||
desc: '',
|
||||
tableId: '',
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: [
|
||||
{
|
||||
nanoid: nanoid(),
|
||||
name: '',
|
||||
desc: '',
|
||||
type: FieldItemType.Text,
|
||||
must_required: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const setTableFieldsListToDefault = () => {
|
||||
tableStructureRef.current?.setTableFieldsList([
|
||||
{
|
||||
nanoid: nanoid(),
|
||||
name: '',
|
||||
desc: '',
|
||||
type: FieldItemType.Text,
|
||||
must_required: false,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const onUseTemplate = () => {
|
||||
setCreateType(CreateType.template);
|
||||
setIsEntry(false);
|
||||
setData({
|
||||
...TEMPLATE_INFO,
|
||||
});
|
||||
};
|
||||
|
||||
const onUseCustom = () => {
|
||||
setCreateType(CreateType.custom);
|
||||
setIsEntry(false);
|
||||
setDataToDefault();
|
||||
};
|
||||
|
||||
const 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 is the initial value, where you need to manually setState to update the subcomponent state
|
||||
// If Modal has been closed early and the subassembly is uninstalled, the ref is empty. You need to add an optional chain to judge.
|
||||
tableStructureRef.current?.setTableFieldsList(
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
res.bot_table_list[0].field_list.map(i => ({
|
||||
name: i.name,
|
||||
desc: i.desc,
|
||||
must_required: i.must_required,
|
||||
type: i.type,
|
||||
nanoid: nanoid(),
|
||||
id: Number(i.id),
|
||||
})),
|
||||
);
|
||||
} else {
|
||||
if (type === SceneType.BotPersona) {
|
||||
Toast.info(I18n.t('recommended_failed'));
|
||||
setDataToDefault();
|
||||
setTableFieldsListToDefault();
|
||||
}
|
||||
if (type === SceneType.ModelDesc) {
|
||||
Toast.warning(I18n.t('generate_failed'));
|
||||
setAIPopoverVisible(true);
|
||||
}
|
||||
}
|
||||
setGenerateTableLoading(false);
|
||||
};
|
||||
|
||||
const handleGenerate = () => {
|
||||
const generate = () => {
|
||||
const { value } = nlTextAreaRef.current || {};
|
||||
if (value) {
|
||||
generateTableByNL(value, SceneType.ModelDesc);
|
||||
}
|
||||
};
|
||||
|
||||
sendTeaEvent(EVENT_NAMES.generate_with_ai_click, {
|
||||
bot_id: botId,
|
||||
need_login: true,
|
||||
have_access: true,
|
||||
});
|
||||
setAIPopoverVisible(false);
|
||||
|
||||
if (
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableStructureRef.current.tableFieldsList.filter(i => i.name).length > 0
|
||||
) {
|
||||
Modal.warning({
|
||||
title: I18n.t('bot_database_ai_replace'),
|
||||
content: I18n.t('bot_database_ai_replace_detailed'),
|
||||
okButtonProps: {
|
||||
type: 'warning',
|
||||
},
|
||||
onOk: () => {
|
||||
generate();
|
||||
},
|
||||
maskClosable: false,
|
||||
icon: <IconWarningSize24 />,
|
||||
});
|
||||
} else {
|
||||
generate();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setAIPopoverVisible(false);
|
||||
setIsPreview(false);
|
||||
setShouldHideDatabaseTableStructureTipsForCurrent(false);
|
||||
setIsDeletedField(false);
|
||||
setContentCheckErrorMsg('');
|
||||
setIsEntry(true);
|
||||
}, [visible]);
|
||||
|
||||
useEffect(() => {
|
||||
setData(database);
|
||||
}, [database]);
|
||||
|
||||
useEffect(() => {
|
||||
setCreateType(NL2DBInfo ? CreateType.recommend : CreateType.custom);
|
||||
}, [NL2DBInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (NL2DBInfo && visible) {
|
||||
generateTableByNL(NL2DBInfo.prompt, SceneType.BotPersona);
|
||||
}
|
||||
}, [NL2DBInfo, visible]);
|
||||
|
||||
const DefaultFooter = (
|
||||
<>
|
||||
{contentCheckErrorMsg ? (
|
||||
<Form.ErrorMessage error={contentCheckErrorMsg} />
|
||||
) : null}
|
||||
{hideTableStructureTips ? null : (
|
||||
<div className={s['modal-modify-tips']}>
|
||||
<div className={s.description}>
|
||||
<IconAlertTriangle className={s['tip-icon']} />
|
||||
<span style={{ textAlign: 'left' }}>{I18n.t('db_edit_tips1')}</span>
|
||||
<span
|
||||
className={s.link}
|
||||
onClick={() => {
|
||||
const lsMap = JSON.parse(
|
||||
mapOfShouldHidingDatabaseTableStructureTips || '{}',
|
||||
);
|
||||
lsMap[botId] = true;
|
||||
setMapOfShouldHidingDatabaseTableStructureTips(
|
||||
JSON.stringify(lsMap),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{I18n.t('db_edit_tips2')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<IconClose
|
||||
onClick={() =>
|
||||
setShouldHideDatabaseTableStructureTipsForCurrent(true)
|
||||
}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={s['modal-table-btn']}>
|
||||
{isDeletedField ? (
|
||||
<Popconfirm
|
||||
title={I18n.t('db_del_field_confirm_title')}
|
||||
content={I18n.t('db_del_field_confirm_info')}
|
||||
okText={I18n.t('db_del_field_confirm_yes')}
|
||||
cancelText={I18n.t('db_del_field_confirm_no')}
|
||||
okType="danger"
|
||||
onConfirm={handleSave}
|
||||
>
|
||||
<BotDebugButton
|
||||
loading={isReadonly}
|
||||
theme="solid"
|
||||
type="primary"
|
||||
readonly={readonly}
|
||||
>
|
||||
{I18n.t('db_edit_save')}
|
||||
</BotDebugButton>
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<BotDebugButton
|
||||
readonly={readonly}
|
||||
loading={isReadonly}
|
||||
theme="solid"
|
||||
type="primary"
|
||||
onClick={handleSave}
|
||||
>
|
||||
{I18n.t('db_edit_save')}
|
||||
</BotDebugButton>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const Entry = (
|
||||
<div className={s['modal-temp']}>
|
||||
<div className={s.entry}>
|
||||
<div
|
||||
className={s['entry-method']}
|
||||
onClick={onUseCustom}
|
||||
data-testid={BotE2e.BotDatabaseAddModalAddCustomBtn}
|
||||
>
|
||||
<Icon svg={<AddSVG />} className={s['entry-method-icon']} />
|
||||
<span className={s['entry-method-title']}>
|
||||
{I18n.t('db_add_table_cust')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{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: There is a problem with the current field id generation rule, so the nanoid is temporarily replaced
|
||||
list.find(j => j.nanoid === i.nanoid),
|
||||
),
|
||||
);
|
||||
}}
|
||||
createType={createType}
|
||||
setContentCheckErrorMsg={setContentCheckErrorMsg}
|
||||
/>
|
||||
</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';
|
||||
|
||||
// Validation Table Name and 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;
|
||||
};
|
||||
|
||||
// Validation 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: {
|
||||
// When the error occurs: When clicking the Save button, a prompt appears. When a row in the table fills in the data, but the required fields are not filled in, an error needs to be reported.
|
||||
if (
|
||||
trigger === 'save' &&
|
||||
!value &&
|
||||
thMapper.find(
|
||||
i =>
|
||||
!!listItem[i.key as keyof TableMemoryItem] && !i.defaultValue,
|
||||
)
|
||||
) {
|
||||
listItem.errorMapper[thKey].push(msg);
|
||||
}
|
||||
// Error reporting and disappearance timing: Required text box After entering the content, the error will disappear immediately
|
||||
if (trigger === 'change' && value) {
|
||||
listItem.errorMapper[thKey] = errTarget.filter(i => i !== msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VerifyType.Unique: {
|
||||
// When the error occurs: When you click the Save button, a prompt appears.
|
||||
if (
|
||||
trigger === 'save' &&
|
||||
value &&
|
||||
list.filter(i => i[thKey] === listItem[thKey]).length !== 1
|
||||
) {
|
||||
listItem.errorMapper[thKey].push(msg);
|
||||
}
|
||||
// Error reporting and disappearance timing: Required text box After entering the content, the error will disappear immediately
|
||||
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: {
|
||||
// Error timing: There is a problem with the naming format. When you lose focus, check the format immediately
|
||||
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: single user mode | read-only mode
|
||||
* Normal: Single user mode | Read-only mode
|
||||
* Expert: Single user mode | Read-only mode | Multi-user mode
|
||||
* Undefined: Read and write modes are not supported
|
||||
*/
|
||||
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> => {
|
||||
// Trigger tableFields validation
|
||||
const validatedTableFieldsList = validateFields(tableFieldsList, 'save');
|
||||
setTableFieldsList(validatedTableFieldsList);
|
||||
|
||||
// Trigger and validate tableBasicInfo
|
||||
try {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
await tableBasicInfoFormRef.current.formApi.validate(['name']);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validation tableFields
|
||||
if (
|
||||
validatedTableFieldsList.find(i =>
|
||||
Object.keys(i.errorMapper || {}).find(
|
||||
j => !!i?.errorMapper?.[j]?.length,
|
||||
),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that tableFields is empty
|
||||
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('; ');
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize ref attribute
|
||||
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],
|
||||
);
|
||||
|
||||
// Custom form item component
|
||||
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',
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
// Verify that the Next button is disabled
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* Built-in field: 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,
|
||||
};
|
||||
|
||||
/**
|
||||
* Built-in field: id
|
||||
*/
|
||||
export const ID_FIELD: TableMemoryItem = {
|
||||
name: 'id',
|
||||
desc: I18n.t('database_240520_01'),
|
||||
type: FieldItemType.Number,
|
||||
must_required: true,
|
||||
nanoid: nanoid(),
|
||||
isSystemField: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Built-in system fields
|
||||
*/
|
||||
export const SYSTEM_FIELDS = [USER_ID_FIELD, ID_FIELD];
|
||||
@@ -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;
|
||||
@@ -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';
|
||||
@@ -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',
|
||||
// recommended table
|
||||
recommend = 'recommend',
|
||||
// Enter natural language to build a table
|
||||
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
);
|
||||