feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -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.
*/
declare const IS_BOE: boolean;
declare const IS_OVERSEA: boolean;

View File

@@ -0,0 +1,247 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable @coze-arch/max-line-per-function */
import React, { type ReactNode, useCallback, useState } from 'react';
import {
type ResourceFolderProps,
type ResourceType,
useProjectId,
useSpaceId,
} from '@coze-project-ide/framework';
import {
BizResourceContextMenuBtnType,
type BizResourceType,
BizResourceTypeEnum,
type ResourceFolderCozeProps,
useOpenResource,
usePrimarySidebarStore,
} from '@coze-project-ide/biz-components';
import { I18n } from '@coze-arch/i18n';
// import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { ResourceCopyScene } from '@coze-arch/bot-api/plugin_develop';
import { PluginDevelopApi } from '@coze-arch/bot-api';
import { From } from '@coze-agent-ide/plugin-shared';
import {
CreateFormPluginModal,
usePluginApisModal,
} from '@coze-agent-ide/bot-plugin/component';
import { Toast } from '@coze-arch/coze-design';
import { useResourceOperation } from './use-resource-operation';
// import { useImportLibraryPlugin } from './use-import-library-plugin';
type UsePluginResourceReturn = Pick<
ResourceFolderCozeProps,
| 'onCustomCreate'
| 'onDelete'
| 'onChangeName'
| 'onAction'
| 'createResourceConfig'
| 'iconRender'
| 'validateConfig'
> & { modals: ReactNode };
const PLUGIN_NAME_MAX_LEN = 30;
export type Validator = Required<
Required<ResourceFolderProps>['validateConfig']
>['customValidator'];
const usePluginResource = (): UsePluginResourceReturn => {
// const display_local_plugin = useSpaceStore(
// store => store.space.display_local_plugin,
// );
const refetch = usePrimarySidebarStore(state => state.refetch);
const spaceId = useSpaceId();
const projectId = useProjectId();
const openResource = useOpenResource();
const [showFormPluginModel, setShowFormPluginModel] = useState(false);
const onCustomCreate: ResourceFolderCozeProps['onCustomCreate'] = (
resourceType,
subType,
) => {
console.log('[ResourceFolder]on custom create>>>', resourceType, subType);
setShowFormPluginModel(true);
};
const onChangeName = useCallback<
Required<ResourceFolderProps>['onChangeName']
>(
async changeNameEvent => {
try {
console.log('[ResourceFolder]on change name>>>', changeNameEvent);
const resp = await PluginDevelopApi.UpdatePluginMeta({
plugin_id: changeNameEvent.id,
name: changeNameEvent.name,
});
console.log('[ResourceFolder]rename plugin response>>>', resp);
} catch (e) {
console.log('[ResourceFolder]rename plugin error>>>', e);
} finally {
refetch();
}
},
[refetch],
);
const onDelete = useCallback(
async (resources: ResourceType[]) => {
try {
console.log('[ResourceFolder]on delete>>>', resources);
const resp = await PluginDevelopApi.DelPlugin({
plugin_id: resources.filter(
r => r.type === BizResourceTypeEnum.Plugin,
)?.[0].res_id,
});
Toast.success(I18n.t('project_plugin_delete_success_toast'));
refetch();
console.log('[ResourceFolder]delete plugin response>>>', resp);
} catch (e) {
console.log('[ResourceFolder]delete plugin error>>>', e);
}
},
[refetch, spaceId],
);
// const { modal: pluginModal, importLibrary } = useImportLibraryPlugin({
// projectId,
// });
const {
node: pluginModal,
open: openPlugin,
close: closePlugin,
} = usePluginApisModal({
from: From.ProjectIde,
pluginApiList: [],
projectId,
showCopyPlugin: true,
showButton: false,
hideCreateBtn: true,
onCopyPluginCallback: ({ pluginID, name }) => {
if (pluginID) {
resourceOperation({
scene: ResourceCopyScene.CopyResourceFromLibrary,
resource: {
id: pluginID,
res_id: pluginID,
name: name ?? '',
},
});
closePlugin();
}
},
onCreateSuccess: async val => {
if (!val?.pluginId) {
return;
}
await refetch?.();
openResource({
resourceType: BizResourceTypeEnum.Plugin,
resourceId: val?.pluginId,
});
closePlugin();
},
isShowStorePlugin: false,
});
const resourceOperation = useResourceOperation({ projectId });
const onAction = (
action: BizResourceContextMenuBtnType,
resource?: BizResourceType,
) => {
console.log('on action>>>', action, resource);
switch (action) {
case BizResourceContextMenuBtnType.ImportLibraryResource:
// return importLibrary();
return openPlugin();
case BizResourceContextMenuBtnType.DuplicateResource:
return resourceOperation({
scene: ResourceCopyScene.CopyProjectResource,
resource,
});
case BizResourceContextMenuBtnType.MoveToLibrary:
return resourceOperation({
scene: ResourceCopyScene.MoveResourceToLibrary,
resource,
});
case BizResourceContextMenuBtnType.CopyToLibrary:
return resourceOperation({
scene: ResourceCopyScene.CopyResourceToLibrary,
resource,
});
default:
console.warn('[PluginResource]unsupported action>>>', action);
break;
}
};
const validateNameBasic: Validator = ({ label }) => {
// 检测 name 是否空
if (!label) {
return I18n.t('create_plugin_modal_name1_error');
}
if (label.length > PLUGIN_NAME_MAX_LEN) {
return I18n.t('project_resource_sidebar_warning_length_exceeds');
}
// 检测 name 的命名规则,国内增加支持中文
if (IS_OVERSEA || IS_BOE) {
if (!/^[\w\s]+$/.test(label)) {
return I18n.t('create_plugin_modal_nameerror');
}
} else {
if (!/^[\w\s\u4e00-\u9fa5]+$/u.test(label)) {
return I18n.t('create_plugin_modal_nameerror_cn');
}
}
return '';
};
return {
onChangeName,
onAction,
onDelete,
onCustomCreate,
// createResourceConfig,
modals: [
<CreateFormPluginModal
projectId={projectId}
isCreate={true}
visible={showFormPluginModel}
onSuccess={async pluginID => {
await refetch?.();
openResource({
resourceType: BizResourceTypeEnum.Plugin,
resourceId: pluginID,
});
}}
onCancel={() => {
setShowFormPluginModel(false);
}}
/>,
pluginModal,
],
validateConfig: {
customValidator: params => validateNameBasic(params) || '',
},
};
};
export default usePluginResource;

View File

@@ -0,0 +1,55 @@
/*
* 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 BizResourceType,
useResourceCopyDispatch,
} from '@coze-project-ide/biz-components';
import { resource_resource_common } from '@coze-arch/bot-api/plugin_develop';
export interface ResourceOperationProps {
projectId: string;
}
export const useResourceOperation = ({ projectId }: ResourceOperationProps) => {
const copyDispatch = useResourceCopyDispatch();
return async ({
scene,
resource,
}: {
scene: resource_resource_common.ResourceCopyScene;
resource?: BizResourceType;
}) => {
try {
console.log(
`[ResourceFolder]workflow resource copy dispatch, scene ${scene}>>>`,
resource,
);
await copyDispatch({
scene,
res_id: resource?.id,
res_type: resource_resource_common.ResType.Plugin,
project_id: projectId,
res_name: resource?.name || '',
});
} catch (e) {
console.error(
`[ResourceFolder]workflow resource copy dispatch, scene ${scene} error>>>`,
e,
);
}
};
};

View 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.
*/
export { default as usePluginResource } from './hooks/use-plugin-resource';

View File

@@ -0,0 +1,204 @@
/*
* 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 React, { type ReactNode, useEffect } from 'react';
import qs from 'qs';
import {
BotPluginStoreProvider,
usePluginStoreInstance,
} from '@coze-studio/bot-plugin-store';
import {
useSpaceId,
useCurrentWidgetContext,
useIDEParams,
useTitle,
useProjectId,
useIDENavigate,
useCommitVersion,
} from '@coze-project-ide/framework';
import { usePrimarySidebarStore } from '@coze-project-ide/biz-components';
import {
PluginDetailPage,
ToolDetailPage,
MockSetDetail,
MockSetList,
} from '@coze-agent-ide/bot-plugin/page';
import { ModuleType } from './types';
interface MainProps {
renderCustomContent?: (params: {
moduleType: ModuleType | undefined;
}) => ReactNode;
}
interface PluginProviderContentProps extends MainProps {
pluginID: string;
spaceID: string;
projectID: string;
refetch: () => Promise<void>;
version: string;
}
// 新增的 PluginProviderContent 组件
const PluginProviderContent: React.FC<PluginProviderContentProps> = ({
pluginID,
spaceID,
projectID,
refetch,
version,
renderCustomContent,
}) => {
const pluginStore = usePluginStoreInstance();
const queryObject = useIDEParams();
const moduleType = queryObject.module as ModuleType | undefined;
const toolID = queryObject.tool_id;
const mocksetID = queryObject.mockset_id;
if (moduleType === ModuleType.MOCKSET_DETAIL && (!toolID || !mocksetID)) {
throw Error('xxxxxxxx');
}
if (moduleType === ModuleType.MOCKSET_LIST && !toolID) {
throw Error('xxxxxxxx');
}
if (moduleType === ModuleType.TOOL && !toolID) {
throw Error('xxxxxxxx');
}
const renderPlugin =
!moduleType ||
![
ModuleType.TOOL,
ModuleType.MOCKSET_LIST,
ModuleType.MOCKSET_DETAIL,
ModuleType.CLOUD_IDE,
].includes(moduleType);
useEffect(() => {
pluginStore?.getState().init();
}, []);
return (
<>
{renderPlugin ? (
<PluginDetailPage projectId={projectID} keepDocTitle />
) : null}
{moduleType === ModuleType.TOOL && toolID ? (
<ToolDetailPage
toolID={toolID}
onDebugSuccessCallback={() => {
refetch();
}}
/>
) : null}
{moduleType === ModuleType.MOCKSET_LIST && toolID ? (
<MockSetList toolID={toolID} />
) : null}
{moduleType === ModuleType.MOCKSET_DETAIL && toolID && mocksetID ? (
<MockSetDetail
toolID={toolID}
mocksetID={mocksetID}
pluginID={pluginID}
spaceID={spaceID}
version={version}
/>
) : null}
{renderCustomContent?.({ moduleType })}
</>
);
};
const Main: React.FC<MainProps> = props => {
const spaceID = useSpaceId();
const projectID = useProjectId();
const IDENav = useIDENavigate();
const { version } = useCommitVersion();
const { uri, widget } = useCurrentWidgetContext();
const title = useTitle();
const refetch = usePrimarySidebarStore(state => state.refetch);
const pluginID = uri?.displayName;
if (!spaceID || !pluginID) {
throw Error('xxxxxxxx');
}
const navBase = `/plugin/${pluginID}`;
return (
<BotPluginStoreProvider
pluginID={pluginID}
spaceID={spaceID}
projectID={projectID}
version={version}
onUpdateDisplayName={displayName => {
widget.setTitle(displayName); // 设置 tab 标题
if (displayName && displayName !== title) {
refetch(); // 更新侧边栏 name
}
}}
onStatusChange={status => {
widget.setUIState(status);
}}
resourceNavigate={{
// eslint-disable-next-line max-params
toResource: (resource, rid, query, opts) =>
rid ? IDENav(`/${resource}/${rid}?${qs.stringify(query)}`, opts) : '',
tool: (tool_id, query, opts) =>
IDENav(
`${navBase}?module=tool&tool_id=${tool_id}&${qs.stringify(query)}`,
opts,
),
mocksetList: (tool_id, query, opts) =>
IDENav(
`${navBase}?module=mockset_list&tool_id=${tool_id}&${qs.stringify(
query,
)}`,
opts,
),
// eslint-disable-next-line max-params
mocksetDetail: (tool_id, mockset_id, query, opts) =>
IDENav(
`${navBase}?module=mockset_detail&tool_id=${tool_id}&mockset_id=${mockset_id}&${qs.stringify(
query,
)}`,
opts,
),
cloudIDE: (query, opts) =>
IDENav(`${navBase}?module=cloud_ide&${qs.stringify(query)}`, opts),
}}
>
<PluginProviderContent
pluginID={pluginID}
spaceID={spaceID}
projectID={projectID}
refetch={refetch}
version={version}
{...props}
/>
</BotPluginStoreProvider>
);
};
export default Main;

View File

@@ -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 enum ModuleType {
TOOL = 'tool',
MOCKSET_LIST = 'mockset_list',
MOCKSET_DETAIL = 'mockset_detail',
CLOUD_IDE = 'cloud_ide',
}