feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { mergeConfig } from 'vite';
|
||||
import svgr from 'vite-plugin-svgr';
|
||||
|
||||
/** @type { import('@storybook/react-vite').StorybookConfig } */
|
||||
const config = {
|
||||
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.tsx'],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-onboarding',
|
||||
'@storybook/addon-interactions',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: 'tag',
|
||||
},
|
||||
viteFinal: config =>
|
||||
mergeConfig(config, {
|
||||
plugins: [
|
||||
svgr({
|
||||
svgrOptions: {
|
||||
native: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
};
|
||||
export default config;
|
||||
@@ -0,0 +1,14 @@
|
||||
/** @type { import('@storybook/react').Preview } */
|
||||
const preview = {
|
||||
parameters: {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
@@ -0,0 +1,5 @@
|
||||
const { defineConfig } = require('@coze-arch/stylelint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
extends: [],
|
||||
});
|
||||
66
frontend/packages/foundation/space-store-adapter/README.md
Normal file
66
frontend/packages/foundation/space-store-adapter/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# @coze-foundation/space-store-adapter
|
||||
|
||||
基座中的空间store
|
||||
|
||||
## Overview
|
||||
|
||||
This package is part of the Coze Studio monorepo and provides state management functionality. It includes store.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installation
|
||||
|
||||
Add this package to your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@coze-foundation/space-store-adapter": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
rush update
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```typescript
|
||||
import { /* exported functions/components */ } from '@coze-foundation/space-store-adapter';
|
||||
|
||||
// Example usage
|
||||
// TODO: Add specific usage examples
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Store
|
||||
|
||||
## API Reference
|
||||
|
||||
### Exports
|
||||
|
||||
- `useSpaceStore`
|
||||
|
||||
|
||||
For detailed API documentation, please refer to the TypeScript definitions.
|
||||
|
||||
## 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,73 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @coze-arch/no-batch-import-or-export, @typescript-eslint/consistent-type-imports
|
||||
import * as zustand from 'zustand';
|
||||
import { act } from '@testing-library/react';
|
||||
|
||||
const { create: actualCreate, createStore: actualCreateStore } =
|
||||
// @ts-expect-error -- UT 忽略
|
||||
await vi.importActual<typeof zustand>('zustand');
|
||||
|
||||
// a variable to hold reset functions for all stores declared in the app
|
||||
export const storeResetFns = new Set<() => void>();
|
||||
|
||||
const createUncurried = <T>(stateCreator: zustand.StateCreator<T>) => {
|
||||
const store = actualCreate(stateCreator);
|
||||
const initialState = store.getState();
|
||||
storeResetFns.add(() => {
|
||||
store.setState(initialState, true);
|
||||
});
|
||||
return store;
|
||||
};
|
||||
|
||||
// when creating a store, we get its initial state, create a reset function and add it in the set
|
||||
export const create = (<T>(stateCreator: zustand.StateCreator<T>) => {
|
||||
console.log('zustand create mock');
|
||||
|
||||
// to support curried version of create
|
||||
return typeof stateCreator === 'function'
|
||||
? createUncurried(stateCreator)
|
||||
: createUncurried;
|
||||
}) as typeof zustand.create;
|
||||
|
||||
const createStoreUncurried = <T>(stateCreator: zustand.StateCreator<T>) => {
|
||||
const store = actualCreateStore(stateCreator);
|
||||
const initialState = store.getState();
|
||||
storeResetFns.add(() => {
|
||||
store.setState(initialState, true);
|
||||
});
|
||||
return store;
|
||||
};
|
||||
|
||||
// when creating a store, we get its initial state, create a reset function and add it in the set
|
||||
export const createStore = (<T>(stateCreator: zustand.StateCreator<T>) => {
|
||||
console.log('zustand createStore mock');
|
||||
|
||||
// to support curried version of createStore
|
||||
return typeof stateCreator === 'function'
|
||||
? createStoreUncurried(stateCreator)
|
||||
: createStoreUncurried;
|
||||
}) as typeof zustand.createStore;
|
||||
|
||||
// reset all stores after each test run
|
||||
afterEach(() => {
|
||||
act(() => {
|
||||
storeResetFns.forEach(resetFn => {
|
||||
resetFn();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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 { SpaceType } from '@coze-arch/bot-api/playground_api';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { useSpaceStore, defaultState } from '../src/space';
|
||||
|
||||
vi.mock('@coze-arch/bot-flags', () => ({
|
||||
getFlags: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/bot-error', () => ({
|
||||
CustomError: vi.fn(),
|
||||
}));
|
||||
|
||||
// FIXME 改为按需 mock
|
||||
vi.mock('@coze-arch/bot-api', () => ({
|
||||
DeveloperApi: {
|
||||
GetUserAuthList: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ data: [{ foo: 1 }] })
|
||||
.mockRejectedValueOnce('error'),
|
||||
ExitSpace: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
.mockResolvedValueOnce({ code: 1 }),
|
||||
TransferSpace: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
.mockResolvedValueOnce({ code: 1 }),
|
||||
},
|
||||
PlaygroundApi: {
|
||||
GetSpaceListV2: vi
|
||||
.fn()
|
||||
// .mockResolvedValueOnce({ code: 0 })
|
||||
// .mockResolvedValueOnce({ code: 1 })
|
||||
// .mockResolvedValueOnce({ code: 0 })
|
||||
// .mockResolvedValueOnce({ code: 0 })
|
||||
// mock 缺失 personal store && 轮询失败
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
.mockResolvedValueOnce({
|
||||
code: 0,
|
||||
data: {
|
||||
bot_space_list: [
|
||||
{
|
||||
id: '123',
|
||||
app_ids: null,
|
||||
name: 'Personal',
|
||||
description: 'Personal',
|
||||
icon_url:
|
||||
'https://lf16-alice-tos-sign.oceanapi-i18n.com/obj/ocean-cloud-tos-sg/FileBizType.BIZ_BOT_SPACE/personal.png?lk3s=50ccb0c5\u0026x-expires=1707120346\u0026x-signature=vvsdzfbTwD2qIYxXa%2BcjGo1H%2Beg%3D',
|
||||
space_type: 1,
|
||||
connectors: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'Cici',
|
||||
icon: 'FileBizType.BIZ_BOT_ICON/7269764022575842322_1700191555077003149.jpg',
|
||||
connector_status: 0,
|
||||
},
|
||||
],
|
||||
hide_operation: false,
|
||||
},
|
||||
],
|
||||
has_personal_space: true,
|
||||
team_space_num: 0,
|
||||
},
|
||||
})
|
||||
.mockRejectedValueOnce(new Error()),
|
||||
SaveSpaceV2: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
.mockResolvedValueOnce({ code: 1 })
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
.mockResolvedValueOnce({ code: 1 }),
|
||||
DeleteSpaceV2: vi.fn(),
|
||||
ExitSpaceV2: vi.fn(),
|
||||
TransferSpaceV2: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
.mockResolvedValueOnce({ code: 1 }),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/logger', () => ({
|
||||
reporter: {
|
||||
errorEvent: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('useSpaceStore', () => {
|
||||
it('should init with default state', () => {
|
||||
const state = useSpaceStore.getState();
|
||||
expect(state).toMatchObject(defaultState);
|
||||
});
|
||||
|
||||
it('reset', () => {
|
||||
useSpaceStore.setState({
|
||||
space: {
|
||||
id: '1',
|
||||
},
|
||||
});
|
||||
useSpaceStore.getState().reset();
|
||||
expect(useSpaceStore.getState()).toMatchObject(defaultState);
|
||||
});
|
||||
|
||||
it('getSpaceId', () => {
|
||||
expect(() => useSpaceStore.getState().getSpaceId()).throw();
|
||||
useSpaceStore.setState({
|
||||
space: {
|
||||
id: '1',
|
||||
},
|
||||
});
|
||||
expect(useSpaceStore.getState().getSpaceId()).toBe('1');
|
||||
});
|
||||
|
||||
it('getPersonalSpaceID', () => {
|
||||
expect(useSpaceStore.getState().getPersonalSpaceID()).toBeUndefined();
|
||||
|
||||
useSpaceStore.setState({
|
||||
spaces: {
|
||||
...useSpaceStore.getState().spaces,
|
||||
bot_space_list: [
|
||||
{ id: '1', space_type: SpaceType.Personal },
|
||||
{ id: '2', space_type: SpaceType.Team },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(useSpaceStore.getState().getPersonalSpaceID()).toBe('1');
|
||||
});
|
||||
|
||||
it('checkSpaceID', () => {
|
||||
expect(useSpaceStore.getState().checkSpaceID('')).toBe(false);
|
||||
|
||||
useSpaceStore.setState({
|
||||
spaces: {
|
||||
...useSpaceStore.getState().spaces,
|
||||
bot_space_list: [{ id: '1' }],
|
||||
},
|
||||
});
|
||||
expect(useSpaceStore.getState().checkSpaceID('1')).toBe(true);
|
||||
});
|
||||
|
||||
it('setSpace', () => {
|
||||
useSpaceStore.getState().setSpace('');
|
||||
expect(useSpaceStore.getState().space).toEqual({ id: '' });
|
||||
|
||||
useSpaceStore.setState({
|
||||
spaces: {
|
||||
...useSpaceStore.getState().spaces,
|
||||
bot_space_list: [{ id: '1' }],
|
||||
},
|
||||
});
|
||||
|
||||
useSpaceStore.getState().setSpace('1');
|
||||
|
||||
expect(useSpaceStore.getState().space).toEqual({ id: '1' });
|
||||
|
||||
useSpaceStore.setState({
|
||||
spaces: {
|
||||
...useSpaceStore.getState().spaces,
|
||||
bot_space_list: [{ id: '2' }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(() => useSpaceStore.getState().setSpace('1')).toThrow(
|
||||
'can not find space: ',
|
||||
);
|
||||
});
|
||||
|
||||
it('createSpace', async () => {
|
||||
const res = await useSpaceStore.getState().createSpace({} as any);
|
||||
expect(res).toBeUndefined();
|
||||
|
||||
expect(useSpaceStore.getState().createSpace({} as any)).rejects.toThrow(
|
||||
'create error:',
|
||||
);
|
||||
});
|
||||
|
||||
it('exitSpace', async () => {
|
||||
const mockExitSpace = vi.mocked(PlaygroundApi.ExitSpaceV2);
|
||||
mockExitSpace.mockResolvedValueOnce({ code: 0, msg: '' });
|
||||
const res = await useSpaceStore.getState().exitSpace({} as any);
|
||||
expect(res).toBeUndefined();
|
||||
});
|
||||
|
||||
it('deleteSpace', async () => {
|
||||
const mockDeleteSpace = vi.mocked(PlaygroundApi.DeleteSpaceV2);
|
||||
mockDeleteSpace.mockResolvedValueOnce({ code: 0, msg: '' });
|
||||
const res = await useSpaceStore.getState().deleteSpace('');
|
||||
expect(res).toBeUndefined();
|
||||
});
|
||||
|
||||
it('fetchSpaces', async () => {
|
||||
useSpaceStore.setState({
|
||||
createSpace: vi.fn(),
|
||||
});
|
||||
await useSpaceStore.getState().fetchSpaces();
|
||||
expect(useSpaceStore.getState().spaces).toEqual({
|
||||
bot_space_list: [],
|
||||
has_personal_space: true,
|
||||
team_space_num: 0,
|
||||
max_team_space_num: 3,
|
||||
});
|
||||
|
||||
const prePromise = useSpaceStore.getState().fetchSpaces(true);
|
||||
await useSpaceStore.getState().fetchSpaces();
|
||||
await prePromise;
|
||||
const expectedValue = {
|
||||
bot_space_list: [
|
||||
{
|
||||
id: '123',
|
||||
app_ids: null,
|
||||
name: 'Personal',
|
||||
description: 'Personal',
|
||||
icon_url:
|
||||
'https://lf16-alice-tos-sign.oceanapi-i18n.com/obj/ocean-cloud-tos-sg/FileBizType.BIZ_BOT_SPACE/personal.png?lk3s=50ccb0c5\u0026x-expires=1707120346\u0026x-signature=vvsdzfbTwD2qIYxXa%2BcjGo1H%2Beg%3D',
|
||||
space_type: 1,
|
||||
connectors: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'Cici',
|
||||
icon: 'FileBizType.BIZ_BOT_ICON/7269764022575842322_1700191555077003149.jpg',
|
||||
connector_status: 0,
|
||||
},
|
||||
],
|
||||
hide_operation: false,
|
||||
},
|
||||
],
|
||||
has_personal_space: true,
|
||||
team_space_num: 0,
|
||||
max_team_space_num: 3,
|
||||
};
|
||||
expect(useSpaceStore.getState().spaces).toEqual(expectedValue);
|
||||
expect(useSpaceStore.getState().createSpace).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(useSpaceStore.getState().fetchSpaces()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"operationSettings": [
|
||||
{
|
||||
"operationName": "test:cov",
|
||||
"outputFolderNames": ["coverage"]
|
||||
},
|
||||
{
|
||||
"operationName": "ts-check",
|
||||
"outputFolderNames": ["./dist"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"codecov": {
|
||||
"coverage": 0,
|
||||
"incrementCoverage": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const { defineConfig } = require('@coze-arch/eslint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
packageRoot: __dirname,
|
||||
preset: 'web',
|
||||
rules: {},
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@coze-foundation/space-store-adapter",
|
||||
"version": "0.0.1",
|
||||
"description": "基座中的空间store",
|
||||
"license": "Apache-2.0",
|
||||
"author": "yuwenbinjie@bytedance.com",
|
||||
"maintainers": [],
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"lint": "eslint ./ --cache",
|
||||
"test": "vitest --run --passWithNoTests",
|
||||
"test:cov": "npm run test -- --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coze-arch/bot-api": "workspace:*",
|
||||
"@coze-arch/bot-error": "workspace:*",
|
||||
"@coze-arch/bot-flags": "workspace:*",
|
||||
"@coze-arch/logger": "workspace:*",
|
||||
"@coze-arch/report-events": "workspace:*",
|
||||
"@coze-foundation/enterprise-store-adapter": "workspace:*",
|
||||
"classnames": "^2.3.2",
|
||||
"immer": "^10.0.3",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coze-arch/bot-typings": "workspace:*",
|
||||
"@coze-arch/eslint-config": "workspace:*",
|
||||
"@coze-arch/stylelint-config": "workspace:*",
|
||||
"@coze-arch/ts-config": "workspace:*",
|
||||
"@coze-arch/vitest-config": "workspace:*",
|
||||
"@testing-library/jest-dom": "^6.1.5",
|
||||
"@testing-library/react": "^14.1.2",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@types/react": "18.2.37",
|
||||
"@types/react-dom": "18.2.15",
|
||||
"@vitest/coverage-v8": "~3.0.5",
|
||||
"react": "~18.2.0",
|
||||
"react-dom": "~18.2.0",
|
||||
"stylelint": "^15.11.0",
|
||||
"vite-plugin-svgr": "~3.3.0",
|
||||
"vitest": "~3.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.2.0",
|
||||
"react-dom": ">=18.2.0"
|
||||
},
|
||||
"// deps": "immer@^10.0.3 为脚本自动补齐,请勿改动"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
vi.stubGlobal('IS_DEV_MODE', false);
|
||||
vi.mock('zustand');
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
export { useSpaceStore } from './space';
|
||||
@@ -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 enum ReportEventNames {
|
||||
EmptySpaceList = 'empty_space_List', // 空间列表为空
|
||||
PollingSpaceList = 'polling_space_list', // 轮训空间列表
|
||||
}
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
/* eslint-disable max-lines-per-function */
|
||||
import { devtools } from 'zustand/middleware';
|
||||
import { create } from 'zustand';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import {
|
||||
type SaveSpaceRet,
|
||||
type SaveSpaceV2Request,
|
||||
type TransferSpaceV2Request,
|
||||
type ExitSpaceV2Request,
|
||||
type SpaceInfo,
|
||||
} from '@coze-arch/bot-api/playground_api';
|
||||
import { type BotSpace, SpaceType } from '@coze-arch/bot-api/developer_api';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { polling, reportSpaceListPollingRes } from './utils';
|
||||
|
||||
interface SpaceStoreState {
|
||||
/** @deprecated try useSpace instead */
|
||||
space: BotSpace;
|
||||
spaceList: BotSpace[];
|
||||
recentlyUsedSpaceList: BotSpace[];
|
||||
loading: false | Promise<SpaceInfo | undefined>;
|
||||
inited?: boolean;
|
||||
createdTeamSpaceNum: number; // 个人创建的团队空间计数
|
||||
maxTeamSpaceNum: number;
|
||||
/** @deprecated 使用 spaceList & maxTeamSpaceNum */
|
||||
spaces: {
|
||||
bot_space_list: BotSpace[];
|
||||
has_personal_space: boolean;
|
||||
team_space_num: number;
|
||||
max_team_space_num: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface SpaceStoreAction {
|
||||
reset: () => void;
|
||||
/** @deprecated get id from url */
|
||||
getSpaceId: () => string;
|
||||
getPersonalSpaceID: () => string | undefined;
|
||||
checkSpaceID: (spaceID: string) => boolean;
|
||||
/** @deprecated 通过 id 索引 */
|
||||
setSpace: (spaceId?: string, isBotDetailIframe?: boolean) => void | never;
|
||||
createSpace: (
|
||||
request: SaveSpaceV2Request,
|
||||
) => Promise<SaveSpaceRet | undefined>;
|
||||
exitSpace: (request: ExitSpaceV2Request) => Promise<string | undefined>;
|
||||
deleteSpace: (id: string) => Promise<string | undefined>;
|
||||
updateSpace: (request: SaveSpaceV2Request) => Promise<{
|
||||
id?: string;
|
||||
check_not_pass?: boolean;
|
||||
}>;
|
||||
transferSpace: (
|
||||
request: TransferSpaceV2Request,
|
||||
) => Promise<string | undefined>;
|
||||
fetchSpaces: (force?: boolean) => Promise<SpaceInfo | undefined>;
|
||||
}
|
||||
|
||||
const DEFAULT_MAXIMUM_SPACE = 3;
|
||||
|
||||
export const defaultState: SpaceStoreState = {
|
||||
space: {},
|
||||
spaceList: [],
|
||||
recentlyUsedSpaceList: [],
|
||||
loading: false,
|
||||
maxTeamSpaceNum: DEFAULT_MAXIMUM_SPACE,
|
||||
createdTeamSpaceNum: 0,
|
||||
inited: false,
|
||||
spaces: {
|
||||
bot_space_list: [],
|
||||
has_personal_space: true,
|
||||
team_space_num: 0,
|
||||
max_team_space_num: DEFAULT_MAXIMUM_SPACE,
|
||||
},
|
||||
};
|
||||
|
||||
export const useSpaceStore = create<SpaceStoreState & SpaceStoreAction>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
...defaultState,
|
||||
reset: () => {
|
||||
set(defaultState, false, 'reset');
|
||||
},
|
||||
getSpaceId: () => {
|
||||
const { id } = get().space;
|
||||
if (!id) {
|
||||
throw new CustomError(
|
||||
REPORT_EVENTS.parmasValidation,
|
||||
'lack space_id',
|
||||
);
|
||||
}
|
||||
return id;
|
||||
},
|
||||
getPersonalSpaceID: () =>
|
||||
get().spaces.bot_space_list?.find(
|
||||
space => space.space_type === SpaceType.Personal,
|
||||
)?.id,
|
||||
|
||||
checkSpaceID: spaceID =>
|
||||
!!get().spaces.bot_space_list?.find(space => space.id === spaceID),
|
||||
|
||||
setSpace: id => {
|
||||
const { space, spaces } = get();
|
||||
|
||||
if (id) {
|
||||
const targetSapce = spaces.bot_space_list.find(s => s.id === id);
|
||||
if (targetSapce) {
|
||||
set({ space: targetSapce }, false, 'setSpace');
|
||||
} else {
|
||||
throw Error(`can not find space: ${id}`);
|
||||
}
|
||||
} else {
|
||||
set(
|
||||
{
|
||||
space: {
|
||||
...space,
|
||||
id: '',
|
||||
},
|
||||
},
|
||||
false,
|
||||
'setSpace',
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
createSpace: async payload => {
|
||||
const res = await PlaygroundApi.SaveSpaceV2(payload);
|
||||
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
} else {
|
||||
throw Error(`create error: ${res.msg}`);
|
||||
}
|
||||
},
|
||||
|
||||
exitSpace: _ => Promise.resolve(undefined),
|
||||
|
||||
deleteSpace: _ => Promise.resolve(undefined),
|
||||
|
||||
updateSpace: _ => Promise.resolve({}),
|
||||
|
||||
transferSpace: () => Promise.resolve(undefined),
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
fetchSpaces: async (force?: boolean) => {
|
||||
const request = async () => {
|
||||
const { data } = await PlaygroundApi.GetSpaceListV2({});
|
||||
return data;
|
||||
};
|
||||
const prePromise = get().loading;
|
||||
const currentPromise = force ? request() : prePromise || request();
|
||||
if (currentPromise !== prePromise) {
|
||||
set(
|
||||
{
|
||||
loading: currentPromise,
|
||||
},
|
||||
false,
|
||||
'fetchSpaces',
|
||||
);
|
||||
} else {
|
||||
return prePromise;
|
||||
}
|
||||
|
||||
let res = await currentPromise;
|
||||
|
||||
if (!res?.has_personal_space) {
|
||||
await get().createSpace({
|
||||
name: 'Personal',
|
||||
description: 'Personal Space',
|
||||
icon_uri: '',
|
||||
space_type: SpaceType.Personal,
|
||||
});
|
||||
const pollingRes = await polling({
|
||||
request,
|
||||
isValid: data => (data?.bot_space_list?.length ?? 0) > 0,
|
||||
});
|
||||
reportSpaceListPollingRes(pollingRes);
|
||||
res = pollingRes.data;
|
||||
}
|
||||
|
||||
const spaceInfo: SpaceStoreState['spaces'] = {
|
||||
bot_space_list: res?.bot_space_list ?? [],
|
||||
has_personal_space: res?.has_personal_space ?? true,
|
||||
team_space_num: res?.team_space_num ?? 0,
|
||||
max_team_space_num: res?.max_team_space_num ?? DEFAULT_MAXIMUM_SPACE,
|
||||
};
|
||||
|
||||
set(
|
||||
{
|
||||
spaceList: spaceInfo.bot_space_list,
|
||||
recentlyUsedSpaceList: res?.recently_used_space_list ?? [],
|
||||
createdTeamSpaceNum: spaceInfo.team_space_num,
|
||||
maxTeamSpaceNum: spaceInfo.max_team_space_num,
|
||||
loading: false,
|
||||
inited: true,
|
||||
spaces: spaceInfo,
|
||||
},
|
||||
false,
|
||||
'fetchSpaces',
|
||||
);
|
||||
|
||||
return res;
|
||||
},
|
||||
}),
|
||||
|
||||
{
|
||||
enabled: IS_DEV_MODE,
|
||||
name: 'botStudio.spaceStore',
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 { reporter } from '@coze-arch/logger';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
|
||||
import { ReportEventNames } from './const';
|
||||
|
||||
const MAX_RETRY = 4;
|
||||
const INTERVAL = 800;
|
||||
|
||||
interface PollingResponse<T = unknown> {
|
||||
data: T;
|
||||
isSuccess: boolean;
|
||||
tryCount: number;
|
||||
}
|
||||
|
||||
export const polling = <T>({
|
||||
request,
|
||||
isValid,
|
||||
maxRetry = MAX_RETRY,
|
||||
interval = INTERVAL,
|
||||
}: {
|
||||
request: () => Promise<T>;
|
||||
isValid: (data: T) => boolean;
|
||||
maxRetry?: number;
|
||||
interval?: number;
|
||||
}): Promise<PollingResponse<T>> => {
|
||||
let tryCount = 0;
|
||||
return new Promise(resolve => {
|
||||
const go = async () => {
|
||||
const data = await request();
|
||||
if (!isValid(data)) {
|
||||
if (++tryCount < maxRetry) {
|
||||
setTimeout(go, interval);
|
||||
} else {
|
||||
resolve({
|
||||
data,
|
||||
isSuccess: false,
|
||||
tryCount,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
resolve({
|
||||
data,
|
||||
isSuccess: true,
|
||||
tryCount,
|
||||
});
|
||||
}
|
||||
};
|
||||
go();
|
||||
});
|
||||
};
|
||||
|
||||
export const reportSpaceListPollingRes = ({
|
||||
isSuccess,
|
||||
tryCount,
|
||||
}: PollingResponse) => {
|
||||
reporter.errorEvent(
|
||||
isSuccess
|
||||
? {
|
||||
eventName: ReportEventNames.PollingSpaceList,
|
||||
error: new CustomError(
|
||||
ReportEventNames.PollingSpaceList,
|
||||
tryCount.toString(),
|
||||
),
|
||||
}
|
||||
: {
|
||||
eventName: ReportEventNames.EmptySpaceList,
|
||||
error: new CustomError(
|
||||
ReportEventNames.EmptySpaceList,
|
||||
'space list is empty',
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
17
frontend/packages/foundation/space-store-adapter/src/typings.d.ts
vendored
Normal file
17
frontend/packages/foundation/space-store-adapter/src/typings.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare const IS_DEV_MODE: boolean;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Meta } from "@storybook/blocks";
|
||||
|
||||
<Meta title="Hello world" />
|
||||
|
||||
<div className="sb-container">
|
||||
<div className='sb-section-title'>
|
||||
# Hello world
|
||||
|
||||
Hello world
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
{`
|
||||
.sb-container {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.sb-section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.sb-section-title {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"compilerOptions": {
|
||||
"types": [],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "./dist/tsconfig.build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../arch/bot-api/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/bot-error/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/bot-flags/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/bot-typings/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/logger/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/report-events/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": "../enterprise-store-adapter/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,25 @@
|
||||
{
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"include": [
|
||||
"__tests__",
|
||||
"__mocks__",
|
||||
"stories",
|
||||
"vitest.config.ts",
|
||||
"tailwind.config.ts",
|
||||
"setup-vitest.ts"
|
||||
],
|
||||
"exclude": ["./dist"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"rootDir": "./",
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/globals"],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true
|
||||
}
|
||||
}
|
||||
@@ -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 { defineConfig } from '@coze-arch/vitest-config';
|
||||
|
||||
export default defineConfig({
|
||||
dirname: __dirname,
|
||||
preset: 'web',
|
||||
test: {
|
||||
coverage: {
|
||||
all: true,
|
||||
exclude: ['src/index.ts'],
|
||||
},
|
||||
setupFiles: ['./setup-vitest.ts'],
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user