feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import React from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { useProjectInfo } from '../../hooks';
|
||||
|
||||
export const BrowserTitle: React.FC = () => {
|
||||
const { projectInfo } = useProjectInfo();
|
||||
return (
|
||||
<Helmet>
|
||||
<title>
|
||||
{I18n.t('project_ide_tab_title', {
|
||||
project_name: projectInfo?.name,
|
||||
})}
|
||||
</title>
|
||||
</Helmet>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
.config-container {
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background: var(--coz-bg-max);
|
||||
border: 1px solid var(--coz-stroke-primary);
|
||||
border-top: none;
|
||||
border-radius: 0 0 8px 8px;
|
||||
|
||||
.primary-sidebar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100%;
|
||||
padding: 0 14px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
.title {
|
||||
font-weight: 500;
|
||||
color: var(--coz-fg-plus);
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
|
||||
margin: 0 8px;
|
||||
padding: 4px 8px 4px 20px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--coz-fg-primary);
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: var(--coz-fg-plus);
|
||||
background-color: var(--coz-mg-primary);
|
||||
}
|
||||
|
||||
&.activate {
|
||||
cursor: pointer;
|
||||
color: var(--coz-fg-plus);
|
||||
background-color: var(--coz-mg-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import {
|
||||
useIDENavigate,
|
||||
useCurrentWidget,
|
||||
type SplitWidget,
|
||||
URI_SCHEME,
|
||||
compareURI,
|
||||
SIDEBAR_CONFIG_URI,
|
||||
useActivateWidgetContext,
|
||||
URI,
|
||||
} from '@coze-project-ide/framework';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
IconCozArrowDown,
|
||||
IconCozArrowUp,
|
||||
IconCozChatSetting,
|
||||
IconCozVariables,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { IconButton } from '@coze-arch/coze-design';
|
||||
|
||||
import { HEADER_HEIGHT } from '../../constants/styles';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const SESSION_CONFIG_STR = '/session';
|
||||
const SESSION_CONFIG_URI = new URI(`${URI_SCHEME}:///session`);
|
||||
const VARIABLE_CONFIG_URI = new URI(`${URI_SCHEME}:///variables`);
|
||||
const VARIABLES_STR = '/variables';
|
||||
|
||||
export const Configuration = () => {
|
||||
const navigate = useIDENavigate();
|
||||
const widget = useCurrentWidget();
|
||||
|
||||
const context = useActivateWidgetContext();
|
||||
|
||||
const [expand, setExpand] = useState(true);
|
||||
|
||||
const handleOpenSession = useCallback(() => {
|
||||
navigate(SESSION_CONFIG_STR);
|
||||
}, []);
|
||||
|
||||
const handleOpenVariables = useCallback(() => {
|
||||
navigate(VARIABLES_STR);
|
||||
}, []);
|
||||
|
||||
const handleSwitchExpand = () => {
|
||||
if (widget) {
|
||||
(widget as SplitWidget).toggleSubWidget(SIDEBAR_CONFIG_URI);
|
||||
}
|
||||
setExpand(!expand);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles['config-container']}>
|
||||
<div
|
||||
className={classnames(
|
||||
styles['primary-sidebar-header'],
|
||||
`h-[${HEADER_HEIGHT}px]`,
|
||||
)}
|
||||
>
|
||||
<div className={styles.title}>{I18n.t('wf_chatflow_143')}</div>
|
||||
<IconButton
|
||||
icon={
|
||||
expand ? (
|
||||
<IconCozArrowDown className="coz-fg-primary" />
|
||||
) : (
|
||||
<IconCozArrowUp className="coz-fg-primary" />
|
||||
)
|
||||
}
|
||||
color="secondary"
|
||||
size="small"
|
||||
onClick={handleSwitchExpand}
|
||||
/>
|
||||
</div>
|
||||
{/* The community version does not currently support conversation management in project, for future expansion */}
|
||||
{IS_OPEN_SOURCE ? null : (
|
||||
<div
|
||||
className={classnames(
|
||||
styles.item,
|
||||
compareURI(context?.uri, SESSION_CONFIG_URI) && styles.activate,
|
||||
)}
|
||||
onClick={handleOpenSession}
|
||||
>
|
||||
<IconCozChatSetting
|
||||
className="coz-fg-plus"
|
||||
style={{ marginRight: 4 }}
|
||||
/>
|
||||
{I18n.t('wf_chatflow_101')}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={classnames(
|
||||
styles.item,
|
||||
compareURI(context?.uri, VARIABLE_CONFIG_URI) && styles.activate,
|
||||
)}
|
||||
onClick={handleOpenVariables}
|
||||
>
|
||||
<IconCozVariables className="coz-fg-plus" style={{ marginRight: 4 }} />
|
||||
{I18n.t('dataide002')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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';
|
||||
|
||||
import { IconCozIllusError } from '@coze-arch/coze-design/illustrations';
|
||||
import { EmptyState } from '@coze-arch/coze-design';
|
||||
|
||||
export const ErrorFallback = () => (
|
||||
<EmptyState
|
||||
size="full_screen"
|
||||
icon={<IconCozIllusError />}
|
||||
title="An error occurred"
|
||||
description="Please try again later."
|
||||
/>
|
||||
);
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import { type WsMessageProps } from '@coze-project-ide/framework/src/types';
|
||||
import {
|
||||
useIDEService,
|
||||
ErrorService,
|
||||
LayoutRestorer,
|
||||
useIDENavigate,
|
||||
useCommitVersion,
|
||||
useWsListener,
|
||||
useGetUIWidgetFromId,
|
||||
getURIPathByPathname,
|
||||
} from '@coze-project-ide/framework';
|
||||
import {
|
||||
BizResourceTypeEnum,
|
||||
ProjectResourceGroupType,
|
||||
usePrimarySidebarStore,
|
||||
} from '@coze-project-ide/biz-components';
|
||||
import {
|
||||
MessageBizType,
|
||||
MessageOperateType,
|
||||
} from '@coze-arch/idl/workflow_api';
|
||||
import { useFlags } from '@coze-arch/bot-flags';
|
||||
|
||||
import { type LayoutRestoreService } from '../../plugins/create-app-plugin/layout-restore-service';
|
||||
|
||||
const leftPanelResourceType = [
|
||||
MessageBizType.Database,
|
||||
MessageBizType.Dataset,
|
||||
MessageBizType.Plugin,
|
||||
MessageBizType.Workflow,
|
||||
];
|
||||
|
||||
/**
|
||||
* IDE 全局逻辑处理
|
||||
*/
|
||||
export const GlobalHandler = ({
|
||||
spaceId,
|
||||
projectId,
|
||||
}: {
|
||||
spaceId: string;
|
||||
projectId: string;
|
||||
}) => {
|
||||
const [error, setError] = useState(false);
|
||||
const navigate = useIDENavigate();
|
||||
const errorService = useIDEService<ErrorService>(ErrorService);
|
||||
const restoreService = useIDEService<LayoutRestoreService>(LayoutRestorer);
|
||||
const { version } = useCommitVersion();
|
||||
const [FLAGS] = useFlags();
|
||||
const path = getURIPathByPathname(window.location.pathname);
|
||||
|
||||
if (error) {
|
||||
throw new Error('project ide global handler error');
|
||||
}
|
||||
|
||||
const fetchResource = usePrimarySidebarStore(state => state.fetchResource);
|
||||
const refetchResource = usePrimarySidebarStore(state => state.refetch);
|
||||
|
||||
const [workflowId, setWorkflowId] = useState('');
|
||||
const [refreshKey, setRefreshKey] = useState('');
|
||||
const widget = useGetUIWidgetFromId(
|
||||
`/${BizResourceTypeEnum.Workflow}/${workflowId}`,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!workflowId) {
|
||||
return;
|
||||
}
|
||||
|
||||
refetchResource(tree => {
|
||||
const workflowResource =
|
||||
tree?.find(
|
||||
group => group.groupType === ProjectResourceGroupType.Workflow,
|
||||
)?.resourceList || [];
|
||||
const nextName = workflowResource?.find(
|
||||
workflow => workflow.id === workflowId,
|
||||
)?.name;
|
||||
if (nextName && widget) {
|
||||
widget.context.widget?.setTitle(nextName);
|
||||
}
|
||||
});
|
||||
}, [workflowId, widget, refreshKey]);
|
||||
|
||||
useWsListener((props: WsMessageProps) => {
|
||||
if (
|
||||
// 社区版暂不支持该功能
|
||||
!FLAGS['bot.automation.project_multi_tab'] ||
|
||||
!leftPanelResourceType.includes(props.bizType)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isUpdateOperate = props.operateType === MessageOperateType.Update;
|
||||
const isRollbackProject = props?.extra?.Scene === 'RollbackProject';
|
||||
if (isUpdateOperate && isRollbackProject) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
const isCreateOperate = props.operateType === MessageOperateType.Create;
|
||||
// 只是创建 workflow 则刷新资源列表
|
||||
const isCreateWorkflow = props?.extra?.methodName === 'CreateWorkflow';
|
||||
// 封装解封场景需要刷新资源列表
|
||||
const isEncapsulateWorkflow =
|
||||
props?.extra?.methodName === 'EncapsulateWorkflow';
|
||||
|
||||
if (isCreateOperate && (isCreateWorkflow || isEncapsulateWorkflow)) {
|
||||
refetchResource();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!props?.resId) {
|
||||
return;
|
||||
}
|
||||
setWorkflowId(props?.resId);
|
||||
setRefreshKey(new Date().getTime().toString());
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchResource(spaceId, projectId, version, tree => {
|
||||
const workflowResource =
|
||||
(tree || []).find(
|
||||
group => group.groupType === ProjectResourceGroupType.Workflow,
|
||||
)?.resourceList || [];
|
||||
const firstWorkflow = workflowResource?.[0];
|
||||
if (!path && restoreService.openFirstWorkflow && firstWorkflow) {
|
||||
navigate(`/workflow/${firstWorkflow.id}`);
|
||||
restoreService.openFirstWorkflow = false;
|
||||
}
|
||||
});
|
||||
}, [spaceId, projectId]);
|
||||
|
||||
useEffect(() => {
|
||||
const errorListener = errorService.onError(() => {
|
||||
setError(true);
|
||||
});
|
||||
return () => {
|
||||
errorListener?.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
.global-loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
||||
background: var(--coz-bg-max);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { Spin } from '@coze-arch/coze-design';
|
||||
import { useIDEService } from '@coze-project-ide/framework';
|
||||
|
||||
import { AppContribution } from '../../plugins/create-app-plugin/app-contribution';
|
||||
|
||||
import css from './index.module.less';
|
||||
|
||||
export const GlobalLoading = () => {
|
||||
const [ready, setReady] = useState(false);
|
||||
const app = useIDEService<AppContribution>(AppContribution);
|
||||
|
||||
useEffect(() => {
|
||||
const disposable = app.onStarted(() => {
|
||||
setReady(true);
|
||||
});
|
||||
return () => disposable.dispose();
|
||||
}, [app]);
|
||||
|
||||
if (ready) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={css['global-loading']}>
|
||||
<Spin />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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, { useState, useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Modal } from '@coze-arch/coze-design';
|
||||
import {
|
||||
useIDEService,
|
||||
WindowService,
|
||||
ViewService,
|
||||
ModalService,
|
||||
ModalType,
|
||||
type CustomTitleType,
|
||||
} from '@coze-project-ide/framework';
|
||||
|
||||
import styles from './styles.module.less';
|
||||
|
||||
export const CloseConfirmModal = () => {
|
||||
const modalService = useIDEService<ModalService>(ModalService);
|
||||
const windowService = useIDEService<WindowService>(WindowService);
|
||||
const viewService = useIDEService<ViewService>(ViewService);
|
||||
|
||||
const currentTitlesRef = useRef<CustomTitleType[]>();
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const handleOk = useCallback(() => {
|
||||
(currentTitlesRef.current || []).forEach(title => {
|
||||
title?.owner?.close();
|
||||
});
|
||||
setVisible(false);
|
||||
}, []);
|
||||
const handleCancel = useCallback(() => {
|
||||
setVisible(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// 浏览器维度的 dispose 监听
|
||||
const chromeDispose = windowService.onBeforeUnload(e => {
|
||||
// 路由判断
|
||||
const titles = viewService.getOpenTitles();
|
||||
const hasUnsaved = titles.some(title => title.saving);
|
||||
|
||||
// 当存在未保存的项的时候,需要阻止
|
||||
if (hasUnsaved) {
|
||||
// 每次浏览器关闭之前都打开阻止关闭的弹窗
|
||||
// 兼容不同浏览器的行为
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.returnValue = '';
|
||||
return '';
|
||||
}
|
||||
});
|
||||
// 资源维度的 dispose 监听
|
||||
const resourceDisposable = modalService.onModalVisibleChange(opt => {
|
||||
const { type, options, visible: vis = true } = opt;
|
||||
if (type === ModalType.CLOSE_CONFIRM) {
|
||||
setVisible(Boolean(vis));
|
||||
currentTitlesRef.current = options;
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
chromeDispose.dispose();
|
||||
resourceDisposable.dispose();
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
type="dialog"
|
||||
title={I18n.t('project_ide_unsaved_changes')}
|
||||
okText={I18n.t('project_ide_quit')}
|
||||
okButtonColor="red"
|
||||
cancelText={I18n.t('project_ide_cancel')}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
maskClosable={false}
|
||||
>
|
||||
<div className={styles.content}>
|
||||
{I18n.t('project_ide_unsaved_describe')}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
.content {
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: var(--coz-fg-secondary);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { ResourceModal } from './resource-modal';
|
||||
import { CloseConfirmModal } from './close-confirm-modal';
|
||||
|
||||
export const GlobalModals = () => (
|
||||
// do something
|
||||
<>
|
||||
{/* 移动资源库全局弹窗 */}
|
||||
<ResourceModal />
|
||||
{/* 保存中资源关闭弹窗 */}
|
||||
<CloseConfirmModal />
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
ModalService,
|
||||
ModalType,
|
||||
useIDEService,
|
||||
} from '@coze-project-ide/framework';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozWarningCircleFillPalette } from '@coze-arch/coze-design/icons';
|
||||
import { Modal } from '@coze-arch/coze-design';
|
||||
import { ResourceCopyScene } from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import { LoopContent } from './loop-content';
|
||||
|
||||
import styles from './styles.module.less';
|
||||
|
||||
export const ResourceModal = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [scene, setScene] = useState<ResourceCopyScene | undefined>(undefined);
|
||||
const [error, setError] = useState<boolean | string>(false);
|
||||
const [resourceName, setResourceName] = useState('');
|
||||
|
||||
const modalService = useIDEService<ModalService>(ModalService);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
modalService.onCloseResourceModal();
|
||||
}, []);
|
||||
|
||||
const handleOk = useCallback(() => {
|
||||
if (error) {
|
||||
modalService.retry();
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
const title = useMemo(() => {
|
||||
switch (scene) {
|
||||
case ResourceCopyScene.CopyResourceFromLibrary:
|
||||
return I18n.t(
|
||||
'resource_process_modal_title_import_resource_from_library',
|
||||
);
|
||||
case ResourceCopyScene.MoveResourceToLibrary:
|
||||
return I18n.t('resource_process_modal_title_move_resource_to_library');
|
||||
case ResourceCopyScene.CopyResourceToLibrary:
|
||||
return I18n.t('resource_process_modal_title_copy_resource_to_library');
|
||||
case ResourceCopyScene.CopyProjectResource:
|
||||
return I18n.t('workflow_add_list_copy');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}, [scene]);
|
||||
|
||||
const errorContent = useMemo(() => {
|
||||
if (typeof error === 'string' && error !== 'no_task_id') {
|
||||
return error;
|
||||
}
|
||||
switch (scene) {
|
||||
case ResourceCopyScene.CopyResourceFromLibrary:
|
||||
return I18n.t('resource_toast_copy_to_project_fail');
|
||||
case ResourceCopyScene.MoveResourceToLibrary:
|
||||
return I18n.t('resource_toast_move_to_library_fail');
|
||||
case ResourceCopyScene.CopyResourceToLibrary:
|
||||
return I18n.t('resource_toast_copy_to_library_fail');
|
||||
case ResourceCopyScene.CopyProjectResource:
|
||||
return I18n.t('project_toast_copy_failed');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}, [scene, error]);
|
||||
|
||||
const content = useMemo(() => {
|
||||
if (error) {
|
||||
return (
|
||||
<div className={styles['error-container']}>
|
||||
<IconCozWarningCircleFillPalette
|
||||
className="coz-fg-hglt-red"
|
||||
fontSize={22}
|
||||
/>
|
||||
{errorContent}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <LoopContent scene={scene} resourceName={resourceName} />;
|
||||
}, [error, errorContent, resourceName, scene]);
|
||||
|
||||
const okText = useMemo(() => {
|
||||
// Retry is not supported in the open-source environment.
|
||||
if (IS_OPEN_SOURCE) {
|
||||
return '';
|
||||
}
|
||||
if (typeof error === 'string') {
|
||||
return '';
|
||||
} else if (error) {
|
||||
return I18n.t('resource_process_modal_retry_button');
|
||||
}
|
||||
return '';
|
||||
}, [error]);
|
||||
|
||||
useEffect(() => {
|
||||
const resourceDisposable = modalService.onModalVisibleChange(
|
||||
({
|
||||
type,
|
||||
visible: vis = true,
|
||||
scene: _scene,
|
||||
resourceName: _resourceName,
|
||||
}) => {
|
||||
if (type === ModalType.RESOURCE) {
|
||||
setVisible(Boolean(vis));
|
||||
setScene(_scene);
|
||||
setResourceName(_resourceName || '');
|
||||
setError(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
const resourceErrorDisposable = modalService.onError(isError => {
|
||||
setError(isError);
|
||||
});
|
||||
return () => {
|
||||
resourceDisposable.dispose();
|
||||
resourceErrorDisposable.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const cancelText = useMemo(() => {
|
||||
// Cancel logic changes in the open-source environment.
|
||||
if (IS_OPEN_SOURCE) {
|
||||
return error ? I18n.t('resource_process_modal_cancel_button') : undefined;
|
||||
}
|
||||
return I18n.t('resource_process_modal_cancel_button');
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
width={384}
|
||||
type="dialog"
|
||||
title={title}
|
||||
okText={okText}
|
||||
onOk={handleOk}
|
||||
cancelText={cancelText}
|
||||
onCancel={handleCancel}
|
||||
maskClosable={false}
|
||||
>
|
||||
<div className={styles.content}>{content}</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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, { useMemo } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Loading } from '@coze-arch/coze-design';
|
||||
import { ResourceCopyScene } from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import styles from './styles.module.less';
|
||||
|
||||
export const LoopContent = ({
|
||||
scene,
|
||||
resourceName,
|
||||
}: {
|
||||
scene?: ResourceCopyScene;
|
||||
resourceName?: string;
|
||||
}) => {
|
||||
const loopMoveText = useMemo(() => {
|
||||
switch (scene) {
|
||||
case ResourceCopyScene.CopyResourceFromLibrary:
|
||||
return I18n.t(
|
||||
'resource_process_modal_text_copying_resource_to_project',
|
||||
{
|
||||
resourceName,
|
||||
},
|
||||
);
|
||||
case ResourceCopyScene.MoveResourceToLibrary:
|
||||
return I18n.t(
|
||||
'resource_process_modal_text_moving_resource_to_library',
|
||||
{
|
||||
resourceName,
|
||||
},
|
||||
);
|
||||
case ResourceCopyScene.CopyResourceToLibrary:
|
||||
return I18n.t(
|
||||
'resource_process_modal_text_copying_resource_to_library',
|
||||
{
|
||||
resourceName,
|
||||
},
|
||||
);
|
||||
case ResourceCopyScene.CopyProjectResource:
|
||||
return I18n.t('project_toast_copying_resource', { resourceName });
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}, [scene, resourceName]);
|
||||
|
||||
const loopSuggestionText = useMemo(() => {
|
||||
if (scene === ResourceCopyScene.MoveResourceToLibrary) {
|
||||
return I18n.t(
|
||||
'resource_process_modal_text_moving_process_interrupt_warning',
|
||||
);
|
||||
}
|
||||
return I18n.t(
|
||||
'resource_process_modal_text_copying_process_interrupt_warning',
|
||||
);
|
||||
}, [scene]);
|
||||
|
||||
return (
|
||||
<div className={styles['description-container']}>
|
||||
<Loading loading={true} wrapperClassName={styles.spin} />
|
||||
<div>{loopMoveText}</div>
|
||||
<div>{loopSuggestionText}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
.content {
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: var(--coz-fg-secondary);
|
||||
|
||||
.error-container {
|
||||
display: flex;
|
||||
flex: 1 0 0;
|
||||
flex-direction: column;
|
||||
gap: 22px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin: 24px 0 22px;
|
||||
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.description-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin: 28px 0 12px;
|
||||
|
||||
text-align: center;
|
||||
|
||||
.spin {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
frontend/packages/project-ide/main/src/components/index.ts
Normal file
30
frontend/packages/project-ide/main/src/components/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { TopBar } from './top-bar';
|
||||
export { PrimarySidebar } from './primary-sidebar';
|
||||
export { widgetTitleRender } from './widget-title';
|
||||
export { WidgetDefaultRenderer } from './widget-default-renderer';
|
||||
export { SidebarExpand } from './sidebar-expand';
|
||||
export { FullScreenButton } from './toolbar/full-screen-button';
|
||||
export { ToolBar } from './toolbar';
|
||||
export { GlobalModals } from './global-modals';
|
||||
export { Configuration } from './configuration';
|
||||
export { ErrorFallback } from './error-fallback';
|
||||
export { GlobalHandler } from './global-handler';
|
||||
export { BrowserTitle } from './browser-title';
|
||||
export { GlobalLoading } from './global-loading';
|
||||
export { UIBuilder } from './ui-builder';
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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, { useState, lazy, Suspense } from 'react';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import { withQueryClient } from '@coze-workflow/base';
|
||||
import { useProjectIDEServices } from '@coze-project-ide/framework';
|
||||
import { useResourceList } from '@coze-project-ide/biz-components';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
IconCozSideExpand,
|
||||
IconCozBinding,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, Button } from '@coze-arch/coze-design';
|
||||
import { useFlags } from '@coze-arch/bot-flags';
|
||||
|
||||
import { ResourceList } from '../resource-list';
|
||||
import { HEADER_HEIGHT } from '../../constants/styles';
|
||||
|
||||
import styles from './styles.module.less';
|
||||
|
||||
const ResourceTreeModal = lazy(() =>
|
||||
import('../resource-tree-modal').then(exps => ({
|
||||
default: exps.ResourceTreeModal,
|
||||
})),
|
||||
);
|
||||
|
||||
const PrimarySidebarCore = ({
|
||||
hideExpand,
|
||||
idPrefix,
|
||||
}: {
|
||||
hideExpand?: boolean;
|
||||
idPrefix?: string;
|
||||
}) => {
|
||||
const projectIDEServices = useProjectIDEServices();
|
||||
const { workflowResource } = useResourceList();
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [FLAGS] = useFlags();
|
||||
|
||||
const handleExpand = () => {
|
||||
projectIDEServices.view.primarySidebar.changeVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles['primary-sidebar']}>
|
||||
<div
|
||||
className={classnames(
|
||||
styles['primary-sidebar-header'],
|
||||
`h-[${HEADER_HEIGHT}px]`,
|
||||
)}
|
||||
>
|
||||
<div className={styles.title}>
|
||||
{I18n.t('project_resource_sidebar_title')}
|
||||
{/* 社区版暂不支持该功能 */}
|
||||
{FLAGS['bot.automation.dependency_tree'] ? (
|
||||
<>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<IconCozBinding />}
|
||||
color="primary"
|
||||
disabled={!workflowResource?.length}
|
||||
onClick={() => setModalVisible(true)}
|
||||
>
|
||||
{I18n.t('reference_graph_entry_button')}
|
||||
</Button>
|
||||
{modalVisible ? (
|
||||
<Suspense fallback={null}>
|
||||
<ResourceTreeModal
|
||||
modalVisible={modalVisible}
|
||||
setModalVisible={setModalVisible}
|
||||
/>
|
||||
</Suspense>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
{hideExpand ? null : (
|
||||
<IconButton
|
||||
data-testid="project-expand-button"
|
||||
icon={<IconCozSideExpand className="coz-fg-primary" />}
|
||||
color="secondary"
|
||||
size="small"
|
||||
onClick={handleExpand}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={styles['resource-list-wrapper']}
|
||||
style={{ height: `calc(100% - ${HEADER_HEIGHT}px)` }}
|
||||
>
|
||||
<ResourceList idPrefix={idPrefix} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export const PrimarySidebar = withQueryClient(PrimarySidebarCore);
|
||||
@@ -0,0 +1,50 @@
|
||||
.primary-sidebar {
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background: var(--coz-bg-max);
|
||||
border: 1px solid var(--coz-stroke-primary);
|
||||
border-bottom: none;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.primary-sidebar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100%;
|
||||
padding: 0 14px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
.title {
|
||||
font-weight: 500;
|
||||
color: var(--coz-fg-plus);
|
||||
}
|
||||
}
|
||||
|
||||
.resource-list-wrapper {
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 10px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* 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, { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
useProjectId,
|
||||
useSpaceId,
|
||||
useCommitVersion,
|
||||
} from '@coze-project-ide/framework';
|
||||
import { useWorkflowResource } from '@coze-project-ide/biz-workflow';
|
||||
import { usePluginResource } from '@coze-project-ide/biz-plugin';
|
||||
import { useDataResource } from '@coze-project-ide/biz-data';
|
||||
import {
|
||||
BizResourceTypeEnum,
|
||||
ProjectResourceGroupType,
|
||||
ResourceFolderCoze,
|
||||
useResourceList,
|
||||
VARIABLE_RESOURCE_ID,
|
||||
} from '@coze-project-ide/biz-components';
|
||||
import {
|
||||
EProjectPermission,
|
||||
useProjectAuth,
|
||||
useProjectRole,
|
||||
} from '@coze-common/auth';
|
||||
import { FormatType } from '@coze-arch/bot-api/knowledge';
|
||||
import {
|
||||
IconCozDatabase,
|
||||
IconCozDocument,
|
||||
IconCozImage,
|
||||
IconCozPlugin,
|
||||
IconCozTable,
|
||||
IconCozVariables,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
|
||||
const datasetIconMap = {
|
||||
[FormatType.Text]: <IconCozDocument />,
|
||||
[FormatType.Table]: <IconCozTable />,
|
||||
[FormatType.Image]: <IconCozImage />,
|
||||
};
|
||||
|
||||
export const ResourceList = ({
|
||||
idPrefix = 'fixed-sidebar',
|
||||
}: {
|
||||
idPrefix?: string;
|
||||
}) => {
|
||||
const {
|
||||
onCustomCreate: createWorkflow,
|
||||
onDelete: deleteWorkflow,
|
||||
onChangeName: changeNameWorkflow,
|
||||
onAction: handleWorkflowAction,
|
||||
createResourceConfig: workflowCreateConfig,
|
||||
iconRender: workflowIconRender,
|
||||
modals: workflowModals,
|
||||
} = useWorkflowResource();
|
||||
|
||||
const {
|
||||
onCustomCreate: createPlugin,
|
||||
onDelete: deletePlugin,
|
||||
onChangeName: changeNamePlugin,
|
||||
onAction: handlePluginAction,
|
||||
// createResourceConfig: workflowCreateConfig,
|
||||
validateConfig: validatePluginConfig,
|
||||
modals: pluginModals,
|
||||
} = usePluginResource();
|
||||
|
||||
const {
|
||||
onCustomCreate: createData,
|
||||
onDelete: deleteData,
|
||||
onChangeName: changeNameData,
|
||||
onAction: handleDataAction,
|
||||
createResourceConfig: dataCreateConfig,
|
||||
modals: dataModals,
|
||||
validateConfig,
|
||||
} = useDataResource();
|
||||
|
||||
const spaceId = useSpaceId();
|
||||
const projectId = useProjectId();
|
||||
const { version: commitVersion } = useCommitVersion();
|
||||
|
||||
let canCreate = useProjectAuth(
|
||||
EProjectPermission.CREATE_RESOURCE,
|
||||
projectId,
|
||||
spaceId,
|
||||
);
|
||||
|
||||
// 存在版本信息,预览状态无法创建资源
|
||||
if (commitVersion) {
|
||||
canCreate = false;
|
||||
}
|
||||
|
||||
const projectRoles = useProjectRole(projectId);
|
||||
const hideMoreBtn = useMemo(
|
||||
// 没有任何权限,或者存在版本信息,需要隐藏操作按钮
|
||||
() => (projectRoles?.length ?? 0) === 0 || !!commitVersion,
|
||||
[projectRoles, commitVersion],
|
||||
);
|
||||
const { workflowResource, pluginResource, dataResource, initLoaded } =
|
||||
useResourceList();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ResourceFolderCoze
|
||||
id={`${idPrefix}_${ProjectResourceGroupType.Workflow}`}
|
||||
groupType={ProjectResourceGroupType.Workflow}
|
||||
defaultResourceType={BizResourceTypeEnum.Workflow}
|
||||
resourceTree={workflowResource}
|
||||
canCreate={canCreate}
|
||||
initLoaded={initLoaded}
|
||||
onChangeName={changeNameWorkflow}
|
||||
onCustomCreate={createWorkflow}
|
||||
onDelete={deleteWorkflow}
|
||||
onAction={handleWorkflowAction}
|
||||
createResourceConfig={workflowCreateConfig}
|
||||
iconRender={workflowIconRender}
|
||||
hideMoreBtn={hideMoreBtn}
|
||||
/>
|
||||
<ResourceFolderCoze
|
||||
id={`${idPrefix}_${ProjectResourceGroupType.Plugin}`}
|
||||
groupType={ProjectResourceGroupType.Plugin}
|
||||
defaultResourceType={BizResourceTypeEnum.Plugin}
|
||||
resourceTree={pluginResource}
|
||||
canCreate={canCreate}
|
||||
initLoaded={initLoaded}
|
||||
// 业务实现
|
||||
onChangeName={changeNamePlugin}
|
||||
onCustomCreate={createPlugin}
|
||||
onDelete={deletePlugin}
|
||||
onAction={handlePluginAction}
|
||||
iconRender={() => <IconCozPlugin />}
|
||||
hideMoreBtn={hideMoreBtn}
|
||||
validateConfig={validatePluginConfig}
|
||||
/>
|
||||
<ResourceFolderCoze
|
||||
id={`${idPrefix}_${ProjectResourceGroupType.Data}`}
|
||||
groupType={ProjectResourceGroupType.Data}
|
||||
resourceTree={dataResource}
|
||||
canCreate={canCreate}
|
||||
initLoaded={initLoaded}
|
||||
createResourceConfig={dataCreateConfig}
|
||||
onChangeName={changeNameData}
|
||||
onDelete={deleteData}
|
||||
onAction={handleDataAction}
|
||||
onCustomCreate={createData}
|
||||
hideMoreBtn={hideMoreBtn}
|
||||
validateConfig={validateConfig}
|
||||
iconRender={({ resource }) => {
|
||||
console.log(resource);
|
||||
if (resource.id === VARIABLE_RESOURCE_ID) {
|
||||
return <IconCozVariables />;
|
||||
}
|
||||
if (resource.type === BizResourceTypeEnum.Database) {
|
||||
return <IconCozDatabase />;
|
||||
}
|
||||
if (resource.type === BizResourceTypeEnum.Knowledge) {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{datasetIconMap[resource.biz_extend?.format_type]}
|
||||
{/**
|
||||
* 1: 启用
|
||||
* 2: 删除,一般没有
|
||||
* 3: 禁用
|
||||
*/}
|
||||
{resource.biz_res_status === 3 ? (
|
||||
<span className="ml-[3px]">已禁用</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
}}
|
||||
/>
|
||||
{workflowModals}
|
||||
{pluginModals}
|
||||
{dataModals}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 ResourceFolderProps,
|
||||
type ResourceType,
|
||||
} from '@coze-project-ide/framework';
|
||||
import {
|
||||
type BizResourceContextMenuBtnType,
|
||||
type ResourceFolderCozeProps,
|
||||
type BizResourceType,
|
||||
} from '@coze-project-ide/biz-components';
|
||||
|
||||
export const useResourceActionsDemo = () => {
|
||||
const onChangeName: ResourceFolderProps['onChangeName'] =
|
||||
async changeNameEvent => {
|
||||
await console.log('[ResourceFolder]on change name>>>', changeNameEvent);
|
||||
};
|
||||
|
||||
const onDelete = async (resources: ResourceType[]) => {
|
||||
await console.log('[ResourceFolder]on delete>>>', resources);
|
||||
};
|
||||
|
||||
const onCreate: ResourceFolderCozeProps['onCreate'] = async (
|
||||
createEvent,
|
||||
subType,
|
||||
) => {
|
||||
await console.log('[ResourceFolder]on create>>>', createEvent, subType);
|
||||
};
|
||||
|
||||
const onCustomCreate: ResourceFolderCozeProps['onCustomCreate'] = async (
|
||||
resourceType,
|
||||
subType,
|
||||
) => {
|
||||
await console.log(
|
||||
'[ResourceFolder]on custom create>>>',
|
||||
resourceType,
|
||||
subType,
|
||||
);
|
||||
};
|
||||
|
||||
const onAction = (
|
||||
action: BizResourceContextMenuBtnType,
|
||||
resource?: BizResourceType,
|
||||
) => {
|
||||
console.log('on action>>>', action, resource);
|
||||
};
|
||||
return {
|
||||
onChangeName,
|
||||
onAction,
|
||||
onDelete,
|
||||
onCreate,
|
||||
onCustomCreate,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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, { useState, useEffect } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import classnames from 'classnames';
|
||||
import {
|
||||
useSpaceId,
|
||||
useProjectId,
|
||||
useCommitVersion,
|
||||
} from '@coze-project-ide/framework';
|
||||
import {
|
||||
useWorkflowResource,
|
||||
ResourceRefTooltip,
|
||||
} from '@coze-project-ide/biz-workflow';
|
||||
import {
|
||||
useResourceList,
|
||||
usePrimarySidebarStore,
|
||||
} from '@coze-project-ide/biz-components';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
WorkflowStorageType,
|
||||
type DependencyTree,
|
||||
} from '@coze-arch/bot-api/workflow_api';
|
||||
import { workflowApi } from '@coze-arch/bot-api';
|
||||
import { IconCozCross, IconCozInfoCircle } from '@coze-arch/coze-design/icons';
|
||||
import { Modal, Button, Tooltip, Loading, Typography } from '@coze-arch/coze-design';
|
||||
|
||||
import { ResourceContent } from './resource-content';
|
||||
|
||||
import s from './styles.module.less';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const DEFAULT_DATA = {
|
||||
node_list: [],
|
||||
};
|
||||
|
||||
export const ResourceTreeModal = ({
|
||||
modalVisible,
|
||||
setModalVisible,
|
||||
}: {
|
||||
modalVisible: boolean;
|
||||
setModalVisible: (v: boolean) => void;
|
||||
}) => {
|
||||
const { selectedResource } = usePrimarySidebarStore(
|
||||
useShallow(store => ({
|
||||
selectedResource: store.selectedResource,
|
||||
})),
|
||||
);
|
||||
const { workflowResource } = useResourceList();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [data, setData] = useState<DependencyTree>(DEFAULT_DATA);
|
||||
const [selectedWorkflowId, setSelectedWorkflowId] = useState(
|
||||
selectedResource || workflowResource?.[0]?.id,
|
||||
);
|
||||
const spaceId = useSpaceId();
|
||||
const projectId = useProjectId();
|
||||
const { version } = useCommitVersion();
|
||||
const { iconRender } = useWorkflowResource();
|
||||
const handleClose = () => {
|
||||
setModalVisible(false);
|
||||
};
|
||||
const handleSwitchWorkflow = (id: string) => {
|
||||
setSelectedWorkflowId(id);
|
||||
};
|
||||
|
||||
const getWorkflowDep = async (id: string) => {
|
||||
setLoading(true);
|
||||
const res = await workflowApi.DependencyTree({
|
||||
type: WorkflowStorageType.Project,
|
||||
project_info: {
|
||||
workflow_id: id,
|
||||
space_id: spaceId,
|
||||
project_id: projectId,
|
||||
draft: version ? false : true,
|
||||
project_version: version ? version : undefined,
|
||||
},
|
||||
});
|
||||
// 兼容先请求后返回场景
|
||||
if (selectedWorkflowId === id) {
|
||||
setData(res?.data || DEFAULT_DATA);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedWorkflowId) {
|
||||
getWorkflowDep(selectedWorkflowId);
|
||||
}
|
||||
}, [selectedWorkflowId]);
|
||||
|
||||
const handleRetry = () => {
|
||||
getWorkflowDep(selectedWorkflowId);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className={s.modal}
|
||||
visible={modalVisible}
|
||||
type="dialog"
|
||||
hasScroll={false}
|
||||
>
|
||||
<div className={s['modal-container']}>
|
||||
<div className={s['workflow-list']}>
|
||||
<div className={s['list-header-container']}>
|
||||
{I18n.t('reference_graph_modal_title')}
|
||||
<Tooltip theme="dark" content={<ResourceRefTooltip />}>
|
||||
<IconCozInfoCircle color="secondary" fontSize={16} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
height: 0,
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<div className={s['list-title']}>
|
||||
{I18n.t(
|
||||
'reference_graph_modal_subtitle_view_relationship_given_workflow',
|
||||
)}
|
||||
</div>
|
||||
<div className={s.list}>
|
||||
{workflowResource.map(workflow => (
|
||||
<div
|
||||
className={classnames(
|
||||
s['list-item'],
|
||||
selectedWorkflowId === workflow.id && s.selected,
|
||||
)}
|
||||
key={workflow.id}
|
||||
onClick={() => handleSwitchWorkflow(workflow.id)}
|
||||
>
|
||||
{iconRender?.({ resource: workflow })}
|
||||
<Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
type: 'tooltip',
|
||||
opts: {
|
||||
position: 'right',
|
||||
theme: 'dark',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{workflow.name}
|
||||
</Text>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s['resource-tree-container']}>
|
||||
{loading ? (
|
||||
<div className={s['loading-container']}>
|
||||
<Loading
|
||||
loading
|
||||
size="large"
|
||||
color="default"
|
||||
className={s.loading}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<ResourceContent
|
||||
data={data}
|
||||
spaceId={spaceId}
|
||||
projectId={projectId}
|
||||
onRetry={handleRetry}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
className={s['close-icon']}
|
||||
color="secondary"
|
||||
size="large"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<IconCozCross fontSize={20} />
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 { ErrorBoundary } from 'react-error-boundary';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { LinkNode } from '@coze-project-ide/biz-workflow';
|
||||
import { ResourceTree, isDepEmpty } from '@coze-common/resource-tree';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type DependencyTree } from '@coze-arch/bot-api/workflow_api';
|
||||
import {
|
||||
IconCozIllusNone,
|
||||
IconCozIllusError,
|
||||
} from '@coze-arch/coze-design/illustrations';
|
||||
import { IconCozRefresh } from '@coze-arch/coze-design/icons';
|
||||
import { EmptyState } from '@coze-arch/coze-design';
|
||||
|
||||
import s from './styles.module.less';
|
||||
|
||||
export const ResourceContent = ({
|
||||
data,
|
||||
spaceId,
|
||||
projectId,
|
||||
onRetry,
|
||||
}: {
|
||||
data: DependencyTree;
|
||||
spaceId: string;
|
||||
projectId: string;
|
||||
onRetry: () => void;
|
||||
}) => {
|
||||
const isEmpty = isDepEmpty(data);
|
||||
const renderLinkNode = useCallback(
|
||||
extraInfo => (
|
||||
<LinkNode extraInfo={extraInfo} spaceId={spaceId} projectId={projectId} />
|
||||
),
|
||||
[spaceId, projectId],
|
||||
);
|
||||
if (isEmpty) {
|
||||
return (
|
||||
<EmptyState
|
||||
size="full_screen"
|
||||
icon={<IconCozIllusNone />}
|
||||
title={I18n.t('reference_graph_tip_current_workflow_has_no_reference')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ErrorBoundary
|
||||
fallback={
|
||||
<EmptyState
|
||||
size="full_screen"
|
||||
icon={<IconCozIllusError />}
|
||||
title={I18n.t('reference_graph_tip_fail_to_load')}
|
||||
buttonProps={{
|
||||
icon: <IconCozRefresh />,
|
||||
color: 'primary',
|
||||
}}
|
||||
buttonText={I18n.t('reference_graph_tip_fail_to_load_retry_needed')}
|
||||
onButtonClick={onRetry}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ResourceTree
|
||||
className={s['resource-tree']}
|
||||
data={data}
|
||||
renderLinkNode={renderLinkNode}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,153 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.modal {
|
||||
:global .semi-modal{
|
||||
width: calc(100vw - 80px) !important;
|
||||
height: calc(100vh - 80px) !important;
|
||||
margin: 40px !important;
|
||||
}
|
||||
|
||||
:global .semi-modal-body-wrapper {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:global .semi-modal-content {
|
||||
padding: 6px !important;
|
||||
}
|
||||
|
||||
:global .semi-modal-body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:global .semi-modal-footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
background-color: var(--coz-bg-max) !important;
|
||||
border: 1px solid var(--coz-stroke-primary) !important;
|
||||
|
||||
&:hover {
|
||||
background-color: #F2F3F7 !important;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #E9EBF2 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background-color: var(--coz-bg-primary);
|
||||
|
||||
.loading {
|
||||
color: var(--coz-fg-dim);
|
||||
}
|
||||
}
|
||||
|
||||
.workflow-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 200px;
|
||||
padding: 6px;
|
||||
|
||||
.list-header-container {
|
||||
display: flex;
|
||||
column-gap: 4px;
|
||||
align-items: center;
|
||||
|
||||
margin: 8px 0 12px 12px;
|
||||
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
margin-bottom: 4px;
|
||||
padding: 4.5px 12px;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--coz-fg-secondary);
|
||||
}
|
||||
|
||||
.list {
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
justify-content: flex-start;
|
||||
|
||||
height: 100%;
|
||||
|
||||
.list-item {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
column-gap: 4px;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
padding: 4px 8px 4px 12px;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
:global svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:global .semi-typography {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--coz-mg-secondary-hovered);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--coz-mg-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--coz-mg-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.resource-tree-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border: 1px solid var(--coz-stroke-primary);
|
||||
border-radius: 8px;
|
||||
|
||||
.resource-tree {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
border: 1px solid var(--coz-stroke-primary);
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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 { useLocation } from 'react-router-dom';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
useLayoutEffect,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
type TabBarToolbar,
|
||||
useCurrentWidget,
|
||||
useProjectIDEServices,
|
||||
useSplitScreenArea,
|
||||
} from '@coze-project-ide/framework';
|
||||
import { usePrimarySidebarStore } from '@coze-project-ide/biz-components';
|
||||
import { IconCozSideExpand } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, Popover } from '@coze-arch/coze-design';
|
||||
|
||||
import { PrimarySidebar } from '../primary-sidebar';
|
||||
|
||||
import styles from './styles.module.less';
|
||||
|
||||
export const SidebarExpand = () => {
|
||||
const projectIDEServices = useProjectIDEServices();
|
||||
const currentWidget = useCurrentWidget<TabBarToolbar>();
|
||||
const direction = useSplitScreenArea(
|
||||
currentWidget.currentURI,
|
||||
currentWidget.tabBar,
|
||||
);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const [visible, setVisible] = useState(
|
||||
projectIDEServices.view.primarySidebar.getVisible(),
|
||||
);
|
||||
|
||||
const canClosePopover = usePrimarySidebarStore(
|
||||
state => state.canClosePopover,
|
||||
);
|
||||
const [popoverVisible, setPopoverVisible] = useState(false);
|
||||
const leaveTimer = useRef<ReturnType<typeof setTimeout>>();
|
||||
const mouseLeaveRef = useRef<boolean>();
|
||||
const handleMouseEnter = () => {
|
||||
setPopoverVisible(true);
|
||||
clearTimeout(leaveTimer.current);
|
||||
mouseLeaveRef.current = false;
|
||||
};
|
||||
const handleMouseLeave = () => {
|
||||
mouseLeaveRef.current = true;
|
||||
if (!canClosePopover) {
|
||||
return;
|
||||
}
|
||||
leaveTimer.current = setTimeout(() => {
|
||||
setPopoverVisible(false);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (canClosePopover && mouseLeaveRef.current) {
|
||||
setPopoverVisible(false);
|
||||
}
|
||||
}, [canClosePopover]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setVisible(projectIDEServices.view.primarySidebar.getVisible());
|
||||
}, [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
// 侧边栏显隐状态切换时,更新按钮状态
|
||||
const disposable = projectIDEServices.view.onSidebarVisibleChange(vis => {
|
||||
setVisible(vis);
|
||||
});
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleExpand = useCallback(() => {
|
||||
projectIDEServices.view.primarySidebar.changeVisible(true);
|
||||
setPopoverVisible(false);
|
||||
}, []);
|
||||
|
||||
// 右边分屏不展示 hover icon
|
||||
if (direction === 'right') {
|
||||
return null;
|
||||
}
|
||||
return visible ? null : (
|
||||
<Popover
|
||||
motion={false}
|
||||
visible={popoverVisible}
|
||||
trigger="custom"
|
||||
zIndex={1000}
|
||||
style={{
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
boxShadow: 'none',
|
||||
padding: 0,
|
||||
}}
|
||||
content={
|
||||
<div
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
className={styles['sidebar-wrapper']}
|
||||
>
|
||||
<PrimarySidebar hideExpand idPrefix={'popover-sidebar'} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
className={styles['icon-button']}
|
||||
icon={<IconCozSideExpand style={{ rotate: '180deg' }} />}
|
||||
color="secondary"
|
||||
onClick={handleExpand}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
.icon-button {
|
||||
margin-right: 6px;
|
||||
|
||||
&&& {
|
||||
background-color: var(--coz-bg-max);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--coz-bg-6);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--coz-bg-8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
width: 282px;
|
||||
height: calc(100vh - 114px);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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, { useState, useEffect, useMemo } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozExpand, IconCozMinimize } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, Tooltip } from '@coze-arch/coze-design';
|
||||
import {
|
||||
type TabBarToolbar,
|
||||
useCurrentWidget,
|
||||
useProjectIDEServices,
|
||||
useSplitScreenArea,
|
||||
Command,
|
||||
useShortcuts,
|
||||
} from '@coze-project-ide/framework';
|
||||
|
||||
import s from './styles.module.less';
|
||||
|
||||
export const FullScreenButton = () => {
|
||||
const projectIDEServices = useProjectIDEServices();
|
||||
const currentWidget = useCurrentWidget<TabBarToolbar>();
|
||||
const { keybinding } = useShortcuts(Command.Default.VIEW_FULL_SCREEN);
|
||||
const direction = useSplitScreenArea(
|
||||
currentWidget.currentURI,
|
||||
currentWidget.tabBar,
|
||||
);
|
||||
|
||||
const [tooltipVisible, setTooltipVisible] = useState(false);
|
||||
|
||||
const [fullScreen, setFullScreen] = useState(
|
||||
projectIDEServices.view.isFullScreenMode,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const disposable = projectIDEServices.view.onFullScreenModeChange(
|
||||
isFullScreen => {
|
||||
setFullScreen(isFullScreen);
|
||||
},
|
||||
);
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const icon = useMemo(() => {
|
||||
if (fullScreen) {
|
||||
return <IconCozMinimize />;
|
||||
} else {
|
||||
return <IconCozExpand />;
|
||||
}
|
||||
}, [fullScreen]);
|
||||
|
||||
const content = useMemo(
|
||||
() => (
|
||||
<div className={s.shortcut}>
|
||||
<div className={s.label}>
|
||||
{fullScreen
|
||||
? I18n.t('project_ide_restore')
|
||||
: I18n.t('project_ide_maximize')}
|
||||
</div>
|
||||
<div className={s.keybinding}>{keybinding}</div>
|
||||
</div>
|
||||
),
|
||||
[fullScreen, keybinding],
|
||||
);
|
||||
|
||||
// 左边分屏不展示全屏按钮
|
||||
if (direction === 'left') {
|
||||
return null;
|
||||
}
|
||||
const handleSwitchFullScreen = () => {
|
||||
projectIDEServices.view.switchFullScreenMode();
|
||||
setTooltipVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={content}
|
||||
position="bottom"
|
||||
// 点击后布局变化,tooltip 需要手动控制消失
|
||||
trigger="custom"
|
||||
visible={tooltipVisible}
|
||||
>
|
||||
<IconButton
|
||||
className={s['icon-button']}
|
||||
icon={icon}
|
||||
color="secondary"
|
||||
onClick={handleSwitchFullScreen}
|
||||
onMouseOver={() => setTooltipVisible(true)}
|
||||
onMouseOut={() => setTooltipVisible(false)}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
.full-screen-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--coz-stroke-primary);
|
||||
}
|
||||
|
||||
.shortcut {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.keybinding {
|
||||
font-weight: 700;
|
||||
color: var(--coz-fg-dim);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
import { type ProjectIDEWidget } from '@coze-project-ide/framework';
|
||||
|
||||
import { ReloadButton } from './reload-button';
|
||||
import { FullScreenButton } from './full-screen-button';
|
||||
|
||||
export const ToolBar = ({ widget }: { widget: ProjectIDEWidget }) => (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<ReloadButton widget={widget} />
|
||||
<FullScreenButton />
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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, { useState, useMemo } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozRefresh } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, Tooltip } from '@coze-arch/coze-design';
|
||||
import {
|
||||
CustomCommand,
|
||||
useShortcuts,
|
||||
type ProjectIDEWidget,
|
||||
} from '@coze-project-ide/framework';
|
||||
|
||||
import s from '../full-screen-button/styles.module.less';
|
||||
|
||||
export const ReloadButton = ({ widget }: { widget: ProjectIDEWidget }) => {
|
||||
const { keybinding } = useShortcuts(CustomCommand.RELOAD);
|
||||
|
||||
const [tooltipVisible, setTooltipVisible] = useState(false);
|
||||
|
||||
const content = useMemo(
|
||||
() => (
|
||||
<div className={s.shortcut}>
|
||||
<div className={s.label}>{I18n.t('refresh_project_tags')}</div>
|
||||
<div className={s.keybinding}>{keybinding}</div>
|
||||
</div>
|
||||
),
|
||||
[keybinding],
|
||||
);
|
||||
|
||||
const handleReload = () => {
|
||||
widget.refresh();
|
||||
widget.context.widget.setUIState('loading');
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={content}
|
||||
position="bottom"
|
||||
// 点击后布局变化,tooltip 需要手动控制消失
|
||||
trigger="custom"
|
||||
visible={tooltipVisible}
|
||||
>
|
||||
<IconButton
|
||||
className={s['icon-button']}
|
||||
icon={<IconCozRefresh />}
|
||||
color="secondary"
|
||||
onClick={handleReload}
|
||||
onMouseOver={() => setTooltipVisible(true)}
|
||||
onMouseOut={() => setTooltipVisible(false)}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -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 { useNavigate } from 'react-router-dom';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { IconCozArrowLeft } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton } from '@coze-arch/coze-design';
|
||||
import { useSpaceId } from '@coze-project-ide/framework';
|
||||
|
||||
export const GoBackButton: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const spaceId = useSpaceId();
|
||||
const handleGoBack = useCallback(() => {
|
||||
navigate(`/space/${spaceId}/develop`);
|
||||
}, [spaceId, navigate]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
color="secondary"
|
||||
icon={<IconCozArrowLeft />}
|
||||
onClick={handleGoBack}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Row, Col } from '@coze-arch/coze-design';
|
||||
import { ModeTab } from '@coze-project-ide/ui-adapter';
|
||||
|
||||
import { ProjectInfo } from './project-info';
|
||||
import { Operators } from './operators';
|
||||
import { GoBackButton } from './go-back-button';
|
||||
|
||||
import styles from './styles.module.less';
|
||||
|
||||
export const TopBar = () => (
|
||||
<div className={styles.container}>
|
||||
<Row className={styles['top-bar']}>
|
||||
<Col span={8} className={styles['left-col']}>
|
||||
{/* 返回按钮 */}
|
||||
<GoBackButton />
|
||||
{/* 项目标题 */}
|
||||
<ProjectInfo />
|
||||
</Col>
|
||||
{/* 海外版暂时不上 uibuilder 切换功能 */}
|
||||
<Col span={8} className={styles['middle-col']}>
|
||||
{IS_OVERSEA ? null : <ModeTab />}
|
||||
</Col>
|
||||
<Col span={8} className={styles['right-col']}>
|
||||
<Operators />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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 { useNavigate } from 'react-router-dom';
|
||||
import React, { type PropsWithChildren, useCallback } from 'react';
|
||||
|
||||
import { PublishButton } from '@coze-studio/project-publish';
|
||||
import {
|
||||
useCopyProjectModal,
|
||||
useDeleteIntelligence,
|
||||
} from '@coze-studio/project-entity-adapter';
|
||||
import { CollapsibleIconButtonGroup } from '@coze-studio/components/collapsible-icon-button';
|
||||
import { LeftContentButtons } from '@coze-project-ide/ui-adapter';
|
||||
import {
|
||||
useProjectId,
|
||||
useSpaceId,
|
||||
useCommitVersion,
|
||||
} from '@coze-project-ide/framework';
|
||||
import {
|
||||
useProjectAuth,
|
||||
EProjectPermission,
|
||||
useProjectRole,
|
||||
} from '@coze-common/auth';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozEye, IconCozMore } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
Divider,
|
||||
Popover,
|
||||
Menu,
|
||||
Toast,
|
||||
Tooltip,
|
||||
Tag,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { useProjectInfo } from '../../../hooks';
|
||||
import { MonetizeConfig } from './monetize';
|
||||
|
||||
export const Operators = () => {
|
||||
const navigate = useNavigate();
|
||||
const spaceId = useSpaceId();
|
||||
const projectId = useProjectId();
|
||||
|
||||
const { version } = useCommitVersion();
|
||||
|
||||
const { modalContextHolder: modalDelete, deleteIntelligence } =
|
||||
useDeleteIntelligence({
|
||||
onDeleteProjectSuccess: () => {
|
||||
Toast.success(I18n.t('project_ide_toast_delete_success'));
|
||||
navigate(`/space/${spaceId}/develop`);
|
||||
},
|
||||
});
|
||||
|
||||
const { modalContextHolder, openModal } = useCopyProjectModal({
|
||||
onSuccess: () => navigate(`/space/${spaceId}/develop`),
|
||||
});
|
||||
const { projectInfo, initialValue } = useProjectInfo();
|
||||
const projectRoles = useProjectRole(projectId);
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
openModal({
|
||||
initialValue: {
|
||||
...initialValue,
|
||||
to_space_id: spaceId,
|
||||
},
|
||||
});
|
||||
}, [initialValue, spaceId]);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
deleteIntelligence({
|
||||
name: initialValue.name || '',
|
||||
projectId: projectInfo?.id || '',
|
||||
});
|
||||
}, [initialValue, projectInfo]);
|
||||
|
||||
const canDelete = useProjectAuth(
|
||||
EProjectPermission.DELETE,
|
||||
projectId,
|
||||
spaceId,
|
||||
);
|
||||
|
||||
return version ? (
|
||||
<Tag prefixIcon={<IconCozEye />}>{I18n.t('app_ide_viewing_archive')}</Tag>
|
||||
) : (
|
||||
<div className="flex items-center justify-end grow gap-[8px] overflow-hidden">
|
||||
{modalContextHolder}
|
||||
{projectRoles.length ? (
|
||||
<>
|
||||
{modalDelete}
|
||||
<LeftContent>
|
||||
<LeftContentButtons />
|
||||
{IS_OVERSEA ? <MonetizeConfig /> : null}
|
||||
</LeftContent>
|
||||
<Divider layout="vertical" className="first:hidden" />
|
||||
<PublishButton
|
||||
spaceId={spaceId}
|
||||
projectId={projectId}
|
||||
hasPublished={Boolean(Number(projectInfo?.publish_time))}
|
||||
/>
|
||||
<Popover
|
||||
trigger="click"
|
||||
className="rounded-[8px]"
|
||||
content={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
className="min-w-[190px] h-[32px] rounded-[4px]"
|
||||
onClick={handleCopy}
|
||||
>
|
||||
{I18n.t('project_ide_duplicate')}
|
||||
</Menu.Item>
|
||||
{/* Tooltip disableFocusListener 失效,等待后续修复完成 */}
|
||||
{canDelete ? (
|
||||
<Menu.Item
|
||||
className="min-w-[190px] h-[32px] rounded-[4px]"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
{I18n.t('project_ide_delete_project')}
|
||||
</Menu.Item>
|
||||
) : (
|
||||
<Tooltip
|
||||
position="left"
|
||||
content={I18n.t('project_delete_permission_tooltips')}
|
||||
>
|
||||
<Menu.Item
|
||||
disabled={true}
|
||||
className="min-w-[190px] h-[32px] rounded-[4px]"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
{I18n.t('project_ide_delete_project')}
|
||||
</Menu.Item>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<IconButton icon={<IconCozMore />} color="secondary" />
|
||||
</Popover>
|
||||
</>
|
||||
) : (
|
||||
<Button onClick={handleCopy}>
|
||||
{I18n.t('project_ide_create_duplicate')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 为了给左侧一个容器用于计算整体宽度以便实现宽度不够时自动隐藏付费配置文案
|
||||
* 同时还要兼顾左侧没有任何内容时自动隐藏 divider 的 first:hidden 写法
|
||||
*/
|
||||
function LeftContent({ children }: PropsWithChildren) {
|
||||
return IS_OVERSEA ? (
|
||||
<CollapsibleIconButtonGroup gap={8}>{children}</CollapsibleIconButtonGroup>
|
||||
) : (
|
||||
children
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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, { useState } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import {
|
||||
MonetizeConfigPanel,
|
||||
type MonetizeConfigValue,
|
||||
} from '@coze-studio/components/monetize';
|
||||
import { CollapsibleIconButton } from '@coze-studio/components/collapsible-icon-button';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozWallet } from '@coze-arch/coze-design/icons';
|
||||
import { Popover } from '@coze-arch/coze-design';
|
||||
import {
|
||||
BotMonetizationRefreshPeriod,
|
||||
MonetizationEntityType,
|
||||
} from '@coze-arch/bot-api/benefit';
|
||||
import { benefitApi } from '@coze-arch/bot-api';
|
||||
import { ProjectRoleType, useProjectRole } from '@coze-common/auth';
|
||||
import { useProjectId } from '@coze-project-ide/framework';
|
||||
|
||||
export function MonetizeConfig() {
|
||||
const projectId = useProjectId();
|
||||
const myRoles = useProjectRole(projectId);
|
||||
const [monetizeConfig, setMonetizeConfig] = useState<MonetizeConfigValue>({
|
||||
isOn: true,
|
||||
freeCount: 0,
|
||||
refreshCycle: BotMonetizationRefreshPeriod.Never,
|
||||
});
|
||||
|
||||
const { data, loading } = useRequest(
|
||||
() =>
|
||||
benefitApi.PublicGetBotMonetizationConfig({
|
||||
entity_id: projectId,
|
||||
entity_type: MonetizationEntityType.Project,
|
||||
}),
|
||||
{
|
||||
onSuccess: res => {
|
||||
setMonetizeConfig({
|
||||
isOn: res.data?.is_enable ?? true,
|
||||
freeCount: res.data?.free_chat_allowance_count ?? 0,
|
||||
refreshCycle:
|
||||
res.data?.refresh_period ?? BotMonetizationRefreshPeriod.Never,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
/** loading 时展示为激活态(默认值) */
|
||||
const btnDisplayOn = loading ? true : monetizeConfig.isOn;
|
||||
|
||||
return (
|
||||
<Popover
|
||||
key={loading || !data?.data ? 'custom' : 'click'}
|
||||
trigger={loading || !data?.data ? 'custom' : 'click'}
|
||||
autoAdjustOverflow={true}
|
||||
content={
|
||||
<MonetizeConfigPanel
|
||||
disabled={!myRoles.includes(ProjectRoleType.Owner)}
|
||||
value={monetizeConfig}
|
||||
onChange={setMonetizeConfig}
|
||||
onDebouncedChange={val => {
|
||||
benefitApi.PublicSaveBotDraftMonetizationConfig({
|
||||
entity_id: projectId,
|
||||
entity_type: MonetizationEntityType.Project,
|
||||
is_enable: val.isOn,
|
||||
free_chat_allowance_count: val.freeCount,
|
||||
refresh_period: val.refreshCycle,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CollapsibleIconButton
|
||||
itemKey={Symbol.for('monetize-btn')}
|
||||
icon={<IconCozWallet className="text-[16px]" />}
|
||||
text={
|
||||
btnDisplayOn ? I18n.t('monetization_on') : I18n.t('monetization_off')
|
||||
}
|
||||
color={btnDisplayOn ? 'highlight' : 'secondary'}
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useProjectAuth, EProjectPermission } from '@coze-common/auth';
|
||||
import { useUpdateProjectModal } from '@coze-studio/project-entity-adapter';
|
||||
import {
|
||||
useSpaceId,
|
||||
useProjectId,
|
||||
useCommitVersion,
|
||||
} from '@coze-project-ide/framework';
|
||||
import {
|
||||
IconCozEdit,
|
||||
IconCozCheckMarkCircleFillPalette,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
CozAvatar,
|
||||
Typography,
|
||||
Skeleton,
|
||||
IconButton,
|
||||
Toast,
|
||||
Popover,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { useProjectInfo } from '../../../hooks';
|
||||
import { InfoContent } from './info-content';
|
||||
|
||||
import styles from './styles.module.less';
|
||||
|
||||
const { Title: COZTitle } = Typography;
|
||||
|
||||
export const ProjectInfo = () => {
|
||||
const {
|
||||
loading,
|
||||
initialValue,
|
||||
projectInfo,
|
||||
updateProjectInfo,
|
||||
publishInfo,
|
||||
ownerInfo,
|
||||
} = useProjectInfo();
|
||||
const spaceId = useSpaceId();
|
||||
const projectId = useProjectId();
|
||||
const { version } = useCommitVersion();
|
||||
const { modalContextHolder, openModal } = useUpdateProjectModal({
|
||||
onSuccess: () => {
|
||||
updateProjectInfo();
|
||||
// 更新 info 信息
|
||||
Toast.success(I18n.t('project_ide_toast_edit_success'));
|
||||
},
|
||||
});
|
||||
|
||||
const canAuthEdit = useProjectAuth(
|
||||
EProjectPermission.EDIT_INFO,
|
||||
projectId || '',
|
||||
spaceId || '',
|
||||
);
|
||||
|
||||
/**
|
||||
* 可编辑判断:
|
||||
* 1. 有编辑权限
|
||||
* 2. 非预览态
|
||||
*/
|
||||
const canEdit = canAuthEdit && !version;
|
||||
|
||||
// 打开 project 编辑弹窗
|
||||
const handleEditProject = useCallback(() => {
|
||||
openModal({
|
||||
initialValue,
|
||||
});
|
||||
}, [initialValue]);
|
||||
|
||||
const hasPublished = publishInfo?.has_published;
|
||||
|
||||
return loading ? (
|
||||
<Skeleton.Title style={{ width: 24, height: 24 }} />
|
||||
) : (
|
||||
<div className={styles['project-info']}>
|
||||
<Popover
|
||||
content={
|
||||
<InfoContent
|
||||
projectInfo={projectInfo}
|
||||
publishInfo={publishInfo}
|
||||
ownerInfo={ownerInfo}
|
||||
spaceId={spaceId}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CozAvatar type="bot" size="small" src={projectInfo?.icon_url} />
|
||||
{hasPublished ? (
|
||||
<div className={styles['check-icon']}>
|
||||
<IconCozCheckMarkCircleFillPalette color="green" />
|
||||
</div>
|
||||
) : null}
|
||||
</Popover>
|
||||
<COZTitle
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: { content: projectInfo?.name },
|
||||
},
|
||||
}}
|
||||
className={styles.title}
|
||||
fontSize="16px"
|
||||
style={{ maxWidth: 320 }}
|
||||
>
|
||||
{projectInfo?.name}
|
||||
</COZTitle>
|
||||
{/* 权限判断 */}
|
||||
{canEdit ? (
|
||||
<IconButton
|
||||
color="secondary"
|
||||
icon={<IconCozEdit />}
|
||||
onClick={handleEditProject}
|
||||
/>
|
||||
) : null}
|
||||
{modalContextHolder}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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 dayjs from 'dayjs';
|
||||
import {
|
||||
type IntelligenceBasicInfo,
|
||||
type IntelligencePublishInfo,
|
||||
} from '@coze-arch/idl/intelligence_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useSpace } from '@coze-arch/foundation-sdk';
|
||||
import { IconCozCheckMarkCircleFillPalette } from '@coze-arch/coze-design/icons';
|
||||
import { CozAvatar, Tag } from '@coze-arch/coze-design';
|
||||
import { type User } from '@coze-arch/bot-api/intelligence_api';
|
||||
|
||||
import styles from './styles.module.less';
|
||||
|
||||
const formatTime = (time?: string) => {
|
||||
const timeNumber = Number(time);
|
||||
if (isNaN(timeNumber)) {
|
||||
return '-';
|
||||
}
|
||||
return dayjs.unix(timeNumber).format('YYYY-MM-DD HH:mm:ss');
|
||||
};
|
||||
|
||||
export const InfoContent = ({
|
||||
spaceId,
|
||||
projectInfo,
|
||||
publishInfo,
|
||||
ownerInfo,
|
||||
}: {
|
||||
spaceId: string;
|
||||
projectInfo?: IntelligenceBasicInfo;
|
||||
publishInfo?: IntelligencePublishInfo;
|
||||
ownerInfo?: User;
|
||||
}) => {
|
||||
const space = useSpace(spaceId);
|
||||
|
||||
if (!projectInfo) {
|
||||
return null;
|
||||
}
|
||||
const createTime = formatTime(projectInfo?.create_time);
|
||||
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<CozAvatar type="bot" size="xl" src={projectInfo?.icon_url} />
|
||||
<div className={styles.title}>{projectInfo?.name}</div>
|
||||
<div className={styles.description}>{projectInfo?.description}</div>
|
||||
<div className={styles['tag-container']}>
|
||||
{space ? (
|
||||
<Tag
|
||||
className={styles.tag}
|
||||
color="primary"
|
||||
prefixIcon={<CozAvatar size="mini" src={space.icon_url} />}
|
||||
>
|
||||
{space.name}
|
||||
</Tag>
|
||||
) : null}
|
||||
{publishInfo?.has_published ? (
|
||||
<Tag
|
||||
className={styles.tag}
|
||||
color="green"
|
||||
prefixIcon={<IconCozCheckMarkCircleFillPalette />}
|
||||
>
|
||||
{I18n.t('Published_1')}
|
||||
</Tag>
|
||||
) : null}
|
||||
</div>
|
||||
{ownerInfo ? (
|
||||
<div className={styles['owner-container']}>
|
||||
<CozAvatar size="micro" src={ownerInfo?.avatar_url} />
|
||||
<div>{ownerInfo?.nickname}</div>
|
||||
<div>@{ownerInfo?.user_unique_name}</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={styles.time}>
|
||||
{I18n.t('project_ide_info_created_on', {
|
||||
time: createTime,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
.project-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
margin: 0 4px 0 8px;
|
||||
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
color: var(--coz-fg-plus);
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
position: absolute;
|
||||
top: 31px;
|
||||
left: 54px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
&&& {
|
||||
background-color: var(--coz-bg-plus);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--coz-bg-6);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--coz-bg-8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
min-width: 252px;
|
||||
max-width: 320px;
|
||||
padding: 16px;
|
||||
|
||||
.title{
|
||||
margin: 16px 0 2px;
|
||||
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.description{
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.tag-container {
|
||||
display: flex;
|
||||
column-gap: 4px;
|
||||
margin-top: 8px;
|
||||
font-weight: 500;
|
||||
|
||||
.tag {
|
||||
padding: 2px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.owner-container {
|
||||
display: flex;
|
||||
column-gap: 4px;
|
||||
align-items: center;
|
||||
|
||||
margin-top: 24px;
|
||||
|
||||
font-size: 12px;
|
||||
color: var(--coz-fg-secondary);
|
||||
}
|
||||
|
||||
.time {
|
||||
margin-top: 6px;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--coz-fg-secondary);
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: fit-content;
|
||||
|
||||
.banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
|
||||
.left-col {
|
||||
display: flex;
|
||||
column-gap: 4px;
|
||||
align-items: center;
|
||||
|
||||
height: 100%;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.middle-col {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.right-col {
|
||||
display: flex;
|
||||
column-gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 { UIBuilder as RawUIBuilder } from '@coze-project-ide/ui-adapter';
|
||||
|
||||
import { useProjectInfo } from '../../hooks';
|
||||
|
||||
export const UIBuilder = () => {
|
||||
const { projectInfo } = useProjectInfo();
|
||||
return <RawUIBuilder projectInfo={projectInfo} />;
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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, { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
useIDEService,
|
||||
ShortcutsService,
|
||||
CommandRegistry,
|
||||
Command,
|
||||
} from '@coze-project-ide/framework';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozDocument } from '@coze-arch/coze-design/icons';
|
||||
import { Image, Button } from '@coze-arch/coze-design';
|
||||
|
||||
import EnWorkflowFrame from '@/assets/en-workflow-frame.png';
|
||||
import EnUIBuilderFrame from '@/assets/en-ui-builder-frame.png';
|
||||
import EnKnowledgeFrame from '@/assets/en-knowledge-frame.png';
|
||||
import CnWorkflowFrame from '@/assets/cn-workflow-frame.png';
|
||||
import CnUIBuilderFrame from '@/assets/cn-ui-builder-frame.png';
|
||||
import CnKnowledgeFrame from '@/assets/cn-knowledge-frame.png';
|
||||
|
||||
import { FullScreenButton } from '../toolbar/full-screen-button';
|
||||
import { SidebarExpand } from '../sidebar-expand';
|
||||
import { ShortcutItem } from './shortcut-item';
|
||||
|
||||
import styles from './styles.module.less';
|
||||
|
||||
// coze 快捷键需要绑定 starling 文案。没有绑定文案的暂时不展示
|
||||
// 避免添加快捷键导致新增误展示
|
||||
const SHOW_SHORTCUTS: string[] = [
|
||||
Command.Default.VIEW_CLOSE_ALL_WIDGET,
|
||||
Command.Default.VIEW_CLOSE_CURRENT_WIDGET,
|
||||
Command.Default.VIEW_CLOSE_OTHER_WIDGET,
|
||||
];
|
||||
|
||||
export const WidgetDefaultRenderer = () => {
|
||||
const shortcutsService = useIDEService<ShortcutsService>(ShortcutsService);
|
||||
const commandRegistry = useIDEService<CommandRegistry>(CommandRegistry);
|
||||
const shortcutsList = shortcutsService.shortcutsHandlers
|
||||
.filter(shortcut => SHOW_SHORTCUTS.includes(shortcut.commandId))
|
||||
.map(shortcut => ({
|
||||
key: shortcut.commandId,
|
||||
label:
|
||||
commandRegistry.getCommand(shortcut.commandId)?.label ||
|
||||
shortcut.commandId,
|
||||
keybinding: shortcutsService.getShortcutByCommandId(shortcut.commandId),
|
||||
}));
|
||||
|
||||
const handleWorkflowDoc = useCallback(() => {
|
||||
window.open('/docs/guides/build_project_in_projectide');
|
||||
}, []);
|
||||
const handleUIBuilderDoc = useCallback(() => {
|
||||
window.open('/docs/guides/build_ui_interface');
|
||||
}, []);
|
||||
const handleDatabaseDoc = useCallback(() => {
|
||||
window.open('/docs/guides/add_resources_to_project');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles['default-container']}>
|
||||
<div className={styles['icon-expand']}>
|
||||
<SidebarExpand />
|
||||
</div>
|
||||
<div className={styles['full-screen']}>
|
||||
<FullScreenButton />
|
||||
</div>
|
||||
<div className={styles.title}>{I18n.t('project_ide_welcome_title')}</div>
|
||||
<div className={styles['sub-title']}>
|
||||
{I18n.t('project_ide_welcome_describe')}
|
||||
</div>
|
||||
<div className={styles.gallery}>
|
||||
<div className={styles['gallery-block']}>
|
||||
<Image
|
||||
preview={false}
|
||||
src={IS_OVERSEA ? EnWorkflowFrame : CnWorkflowFrame}
|
||||
width={320}
|
||||
height={160}
|
||||
/>
|
||||
<div className={styles['gallery-title']}>
|
||||
{I18n.t('project_ide_welcome_workflow_title')}
|
||||
</div>
|
||||
<div className={styles['gallery-description']}>
|
||||
{I18n.t('project_ide_welcome_workflow_describe')}
|
||||
</div>
|
||||
<Button
|
||||
className={styles['doc-search']}
|
||||
icon={<IconCozDocument />}
|
||||
color="primary"
|
||||
onClick={handleWorkflowDoc}
|
||||
>
|
||||
{I18n.t('project_ide_view_document')}
|
||||
</Button>
|
||||
</div>
|
||||
{IS_OVERSEA || IS_OPEN_SOURCE ? null : (
|
||||
<div className={styles['gallery-block']}>
|
||||
<Image
|
||||
preview={false}
|
||||
src={IS_OVERSEA ? EnUIBuilderFrame : CnUIBuilderFrame}
|
||||
width={320}
|
||||
height={160}
|
||||
/>
|
||||
<div className={styles['gallery-title']}>
|
||||
{I18n.t('project_ide_welcome_ui_builder_title')}
|
||||
</div>
|
||||
<div className={styles['gallery-description']}>
|
||||
{I18n.t('project_ide_welcome_ui_builder_describe')}
|
||||
</div>
|
||||
<Button
|
||||
className={styles['doc-search']}
|
||||
icon={<IconCozDocument />}
|
||||
color="primary"
|
||||
onClick={handleUIBuilderDoc}
|
||||
>
|
||||
{I18n.t('project_ide_view_document')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles['gallery-block']}>
|
||||
<Image
|
||||
preview={false}
|
||||
src={IS_OVERSEA ? EnKnowledgeFrame : CnKnowledgeFrame}
|
||||
width={320}
|
||||
height={160}
|
||||
/>
|
||||
<div className={styles['gallery-title']}>
|
||||
{I18n.t('project_ide_welcome_db_title')}
|
||||
</div>
|
||||
<div className={styles['gallery-description']}>
|
||||
{I18n.t('project_ide_welcome_db_describ')}
|
||||
</div>
|
||||
<Button
|
||||
className={styles['doc-search']}
|
||||
icon={<IconCozDocument />}
|
||||
color="primary"
|
||||
onClick={handleDatabaseDoc}
|
||||
>
|
||||
{I18n.t('project_ide_view_document')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles['shortcuts-list']}>
|
||||
{shortcutsList.map(item => (
|
||||
<ShortcutItem key={item.key} item={item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import styles from './styles.module.less';
|
||||
|
||||
export const ShortcutItem = ({
|
||||
item,
|
||||
}: {
|
||||
item: {
|
||||
key: string;
|
||||
label: string;
|
||||
keybinding: string[][];
|
||||
};
|
||||
}) => {
|
||||
const { key, label, keybinding } = item;
|
||||
|
||||
return (
|
||||
<div className={styles['shortcut-item']} key={key}>
|
||||
<div className={styles.label}>{label}</div>
|
||||
<div className={styles.keybinding}>
|
||||
{keybinding.map(bindings =>
|
||||
bindings.map(binding => (
|
||||
<div key={binding} className={styles['keybinding-block']}>
|
||||
{binding}
|
||||
</div>
|
||||
)),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
.shortcut-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.keybinding {
|
||||
display: flex;
|
||||
|
||||
.keybinding-block {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
margin-left: 6px;
|
||||
|
||||
font-family: "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
color: var(--coz-fg-secondary);
|
||||
|
||||
background: var(--coz-mg-primary);
|
||||
border: 0.5px solid var(--coz-stroke-primary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
.default-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background: rgba(0, 0, 0, 3%);
|
||||
|
||||
img {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
margin-bottom: 40px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: var(--coz-fg-secondary, rgba(6, 7, 9, 50%));
|
||||
}
|
||||
|
||||
.gallery {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
.gallery-block {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 320px;
|
||||
|
||||
.gallery-image {
|
||||
flex-shrink: 0;
|
||||
border: 1px solid var(--coz-stroke-primary);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.gallery-title {
|
||||
margin: 12px 0 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
font-weight: 22px;
|
||||
}
|
||||
|
||||
.gallery-description {
|
||||
font-size: 14px;
|
||||
font-weight: 20px;
|
||||
color: var(--coz-fg-secondary, rgba(6, 7, 9, 5%));
|
||||
}
|
||||
|
||||
.doc-search {
|
||||
margin-top: 12px;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gallery-block:hover > .doc-search {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.item {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 320px;
|
||||
height: 48px;
|
||||
margin-bottom: 16px;
|
||||
padding: 8px 12px 8px 8px;
|
||||
|
||||
background-color: var(--coz-bg-max);
|
||||
border: 1px solid var(--coz-stroke-primary);
|
||||
border-radius: 8px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--coz-bg-6);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--coz-bg-8);
|
||||
}
|
||||
|
||||
.item-pre {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
margin-left: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-expand {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
.full-screen {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.shortcuts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 320px;
|
||||
margin-top: 48px;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
import { type ReactWidget } from '@coze-project-ide/framework';
|
||||
|
||||
interface WidgetFallbackProps {
|
||||
widget: ReactWidget;
|
||||
}
|
||||
|
||||
export const WidgetFallback: React.FC<WidgetFallbackProps> = ({ widget }) => (
|
||||
<div>Widget error: {widget.id}</div>
|
||||
);
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
import {
|
||||
IconCozCrossFill,
|
||||
IconCozWarningCircleFill,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { Typography, Loading, Skeleton } from '@coze-arch/coze-design';
|
||||
import {
|
||||
type TitlePropsType,
|
||||
DISABLE_HANDLE_EVENT,
|
||||
Command,
|
||||
type ProjectIDEWidget,
|
||||
} from '@coze-project-ide/framework';
|
||||
|
||||
import styles from './styles.module.less';
|
||||
|
||||
export const WidgetTitle: React.FC<TitlePropsType> = ({
|
||||
commandRegistry,
|
||||
title,
|
||||
widget,
|
||||
uiState,
|
||||
registry,
|
||||
}) => {
|
||||
const [tabHovered, setTabHovered] = useState(false);
|
||||
const renderIcon = useMemo(() => {
|
||||
if (!registry?.renderIcon || typeof registry?.renderIcon !== 'function') {
|
||||
return null;
|
||||
}
|
||||
return registry.renderIcon((widget as ProjectIDEWidget).context);
|
||||
}, [registry]);
|
||||
|
||||
const renderTitle = useMemo(() => {
|
||||
if (tabHovered) {
|
||||
return (
|
||||
<IconCozCrossFill
|
||||
className="coz-fg-secondary"
|
||||
style={{ fontSize: 16 }}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
commandRegistry.executeCommand(
|
||||
Command.Default.VIEW_SAVING_WIDGET_CLOSE_CONFIRM,
|
||||
[widget?.title],
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// 没有标题还在骨架屏阶段
|
||||
if (!title) {
|
||||
return null;
|
||||
} else if (uiState === 'saving') {
|
||||
return <Loading size="mini" loading={true} />;
|
||||
} else if (uiState === 'error') {
|
||||
return <IconCozWarningCircleFill className="text-lg coz-fg-hglt-red" />;
|
||||
}
|
||||
return null;
|
||||
}, [uiState, widget, tabHovered, commandRegistry, title]);
|
||||
|
||||
const handleTabHover = useCallback(() => {
|
||||
setTabHovered(true);
|
||||
}, []);
|
||||
|
||||
const handleTabBlur = useCallback(() => {
|
||||
setTabHovered(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles['title-container']}
|
||||
onMouseOver={handleTabHover}
|
||||
onMouseLeave={handleTabBlur}
|
||||
>
|
||||
{uiState === 'loading' || !title ? (
|
||||
<div className={styles['widget-title']}>
|
||||
<div className={styles['title-label']}>
|
||||
<Skeleton.Title style={{ width: '100px' }} />
|
||||
</div>
|
||||
<div className={cls(styles['close-icon'], DISABLE_HANDLE_EVENT)}>
|
||||
{renderTitle}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles['widget-title']}>
|
||||
<div className={styles['title-label']}>
|
||||
<div className={styles['label-icon']}>{renderIcon}</div>
|
||||
<div className={styles['label-text']}>
|
||||
<Typography.Text ellipsis={{ showTooltip: true }}>
|
||||
{title}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cls(styles['close-icon'], DISABLE_HANDLE_EVENT)}>
|
||||
{renderTitle}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const widgetTitleRender = props => <WidgetTitle {...props} />;
|
||||
@@ -0,0 +1,43 @@
|
||||
.title-container {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.widget-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
:global {
|
||||
.semi-spin.coz-loading-wrapper {
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title-label {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
column-gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.label-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
width: 0;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
Reference in New Issue
Block a user