feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 75 KiB |
BIN
frontend/packages/project-ide/main/src/assets/coz-avatar.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 74 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="coz_send_fill">
|
||||
<path id="Union" d="M17.8476 11.5956C18.2478 11.3775 18.4981 10.9583 18.4981 10.4993C18.4981 10.0403 18.2478 9.62307 17.8476 9.40495L3.3342 1.54702C2.94728 1.33649 2.49018 1.34597 2.11084 1.57168C1.73151 1.79738 1.50391 2.19758 1.50391 2.6414L3.10518 8.89575L9.91326 9.89239C10.3035 9.89239 10.6188 10.1636 10.6188 10.4993C10.6188 10.835 10.3035 11.1063 9.91326 11.1063C6.07652 11.6687 3.80806 11.9987 3.1079 12.0961L1.50391 18.3573C1.50391 18.8011 1.73151 19.2013 2.11084 19.4289C2.49018 19.6546 2.94728 19.6641 3.3342 19.4535L17.8476 11.5956Z" fill="url(#paint0_linear_491_89054)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_491_89054" x1="10.001" y1="1.39551" x2="10.001" y2="19.6051" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A700FA"/>
|
||||
<stop offset="1" stop-color="#5147FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 945 B |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 65 KiB |
@@ -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
@@ -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;
|
||||
}
|
||||
17
frontend/packages/project-ide/main/src/constants/styles.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const HEADER_HEIGHT = 44;
|
||||
17
frontend/packages/project-ide/main/src/global.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/// <reference types='@coze-arch/bot-typings' />
|
||||
17
frontend/packages/project-ide/main/src/hooks/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { useProjectInfo } from './use-project-info';
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
type User,
|
||||
type IntelligenceBasicInfo,
|
||||
type IntelligencePublishInfo,
|
||||
} from '@coze-arch/idl/intelligence_api';
|
||||
import { type ProjectFormValues } from '@coze-studio/project-entity-adapter';
|
||||
import { useIDEService, useIDEGlobalStore } from '@coze-project-ide/framework';
|
||||
|
||||
import { ProjectInfoService } from '../plugins/create-app-plugin/project-info-service';
|
||||
|
||||
export const useProjectInfo = () => {
|
||||
const projectInfoService =
|
||||
useIDEService<ProjectInfoService>(ProjectInfoService);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [projectInfo, setProjectInfo] = useState<
|
||||
IntelligenceBasicInfo | undefined
|
||||
>(projectInfoService.projectInfo?.projectInfo);
|
||||
const [publishInfo, setPublishInfo] = useState<
|
||||
IntelligencePublishInfo | undefined
|
||||
>(projectInfoService?.projectInfo?.publishInfo);
|
||||
const [ownerInfo, setOwnerInfo] = useState<User | undefined>(
|
||||
projectInfoService?.projectInfo?.ownerInfo,
|
||||
);
|
||||
const [initialValue, setInitialValue] = useState<ProjectFormValues>(
|
||||
projectInfoService.initialValue,
|
||||
);
|
||||
|
||||
const { patch } = useIDEGlobalStore(store => ({
|
||||
patch: store.patch,
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (projectInfoService.projectInfo) {
|
||||
setLoading(false);
|
||||
}
|
||||
patch({ projectInfo: { projectInfo, publishInfo, ownerInfo } });
|
||||
const projectDisposable = projectInfoService.onProjectInfoUpdated(() => {
|
||||
setLoading(false);
|
||||
setProjectInfo(projectInfoService.projectInfo?.projectInfo);
|
||||
setPublishInfo(projectInfoService.projectInfo?.publishInfo);
|
||||
setOwnerInfo(projectInfoService.projectInfo?.ownerInfo);
|
||||
patch({ projectInfo: projectInfoService.projectInfo });
|
||||
setInitialValue(projectInfoService.initialValue);
|
||||
});
|
||||
return () => {
|
||||
projectDisposable?.dispose?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
loading,
|
||||
initialValue,
|
||||
projectInfo,
|
||||
ownerInfo,
|
||||
publishInfo,
|
||||
updateProjectInfo:
|
||||
projectInfoService.updateProjectInfo.bind(projectInfoService),
|
||||
};
|
||||
};
|
||||
254
frontend/packages/project-ide/main/src/index.less
Normal file
@@ -0,0 +1,254 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
|
||||
/** 覆盖 flowide 默认样式 **/
|
||||
|
||||
body {
|
||||
overscroll-behavior: none;
|
||||
background: var(--coz-bg-primary);
|
||||
}
|
||||
|
||||
.flowide-container {
|
||||
padding: 0 8px 8px;
|
||||
background: var(--coz-bg-primary) !important;
|
||||
|
||||
// IDE 内部画布颜色色值和 tabbar 对齐
|
||||
.gedit-playground {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.project-ide-workflow-playground {
|
||||
background-color: rgba(0, 0, 0, 3%) !important;
|
||||
}
|
||||
|
||||
#flowide-top-bar {
|
||||
min-height: 56px!important;
|
||||
background: var(--coz-bg-primary) !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
#flowide-main-panel {
|
||||
background: var(--coz-bg-primary) !important;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
#flowide-secondary-sidebar {
|
||||
min-width: 250px;
|
||||
max-width: 250px;
|
||||
background: var(--coz-bg-primary) !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.lm-DockPanel-widget {
|
||||
min-width: 360px !important;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
.lm-BoxPanel #flowide-primary-sidebar {
|
||||
width: 268px !important;
|
||||
min-width: 268px!important;
|
||||
max-width: 500px!important;
|
||||
|
||||
background: var(--coz-bg-primary) !important;
|
||||
border: none !important;
|
||||
|
||||
.lm-SplitPanel-child {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.lm-SplitPanel-handle {
|
||||
background: linear-gradient(to right, white 6px, var(--coz-stroke-primary) 6px, var(--coz-stroke-primary) calc(100% - 6px), white calc(100% - 6px)) !important;
|
||||
border-right: 1px solid var(--coz-stroke-primary);
|
||||
border-left: 1px solid var(--coz-stroke-primary);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
|
||||
&::after {
|
||||
min-height: 4px !important;
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.lm-SplitPanel-handle:hover::after {
|
||||
background: linear-gradient(to right, white 6px, var(--coz-stroke-hglt) 6px, var(--coz-stroke-hglt) calc(100% - 6px), white calc(100% - 6px)) !important;
|
||||
}
|
||||
|
||||
.lm-SplitPanel-handle:active::after {
|
||||
background: linear-gradient(to right, white 6px, var(--coz-stroke-hglt) 6px, var(--coz-stroke-hglt) calc(100% - 6px), white calc(100% - 6px)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.lm-TabBar {
|
||||
height: 44px!important;
|
||||
min-height: 44px!important;
|
||||
max-height: 44px!important;
|
||||
padding: 6px 8px;
|
||||
|
||||
background: rgba(0, 0, 0, 3%) !important;
|
||||
border-radius: 8px 8px 0 0!important;
|
||||
}
|
||||
|
||||
// 分屏遮盖层
|
||||
.lm-DockPanel-overlay {
|
||||
margin-top: 44px;
|
||||
background: rgba(148, 152, 247, 16%);
|
||||
border: none;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
.lm-DockPanel-handle {
|
||||
opacity: 0;
|
||||
background: transparent !important;
|
||||
transition: opacity 0.35s ease-out;
|
||||
|
||||
// 边上预留 1px 底色边
|
||||
// 之所以不直接改变 handle width,是因为 handle width 和激活拖拽区域判断有关。直接更改底部颜色。
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background: linear-gradient(to right, var(--coz-bg-plus) 0, var(--coz-bg-plus) 1px, var(--coz-stroke-hglt) 1px, var(--coz-stroke-hglt) 5px, var(--coz-bg-plus) 5px) !important;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 1;
|
||||
background: linear-gradient(to right, var(--coz-bg-plus) 0, var(--coz-bg-plus) 1px, var(--coz-stroke-hglt) 1px, var(--coz-stroke-hglt) 5px, var(--coz-bg-plus) 5px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.lm-TabBar-tab {
|
||||
position: relative;
|
||||
|
||||
overflow: visible !important;
|
||||
|
||||
min-height: 32px!important;
|
||||
max-height: 32px!important;
|
||||
margin: 0 4px !important;
|
||||
padding: 0 !important;
|
||||
|
||||
border-radius: 6px;
|
||||
|
||||
&:not(:first-child)::after {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: -4px;
|
||||
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
|
||||
background: var(--coz-stroke-primary);
|
||||
}
|
||||
|
||||
// tabbar 设计稿地址:https://www.figma.com/design/EsMau3yKaVNb5q2HESGTdu/Coze-2.0-Redesign?node-id=4994-35024&node-type=canvas&t=us9tc6NhroPJoYVy-0
|
||||
// hover、activate 前后的 tab 不展示竖线
|
||||
&.lm-mod-current &:not(:first-child)::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.lm-mod-current + .lm-TabBar-tab:not(:first-child)::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover + .lm-TabBar-tab {
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.lm-TabBar-tabCloseIcon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.lm-TabBar-tab:hover:not(.lm-mod-current) {
|
||||
background: var(--coz-mg-secondary-hovered);
|
||||
}
|
||||
|
||||
.lm-TabBar-tab.lm-mod-current {
|
||||
&::before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.lm-TabBar-tabIcon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.lm-mod-drag-image {
|
||||
position: relative;
|
||||
|
||||
overflow: visible !important;
|
||||
|
||||
min-height: 32px!important;
|
||||
max-height: 32px!important;
|
||||
margin: 0 4px !important;
|
||||
padding: 0 !important;
|
||||
|
||||
border-radius: 6px;
|
||||
|
||||
.flow-tab-icon-label {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.flow-TabBar-tabLabel-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: keep-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
.lm-TabBar-tabIcon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flow-tab-icon-label {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.flow-Menu {
|
||||
.flow-Menu-item {
|
||||
.flow-Menu-itemLabel {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.flow-Menu-itemShortcut {
|
||||
font-weight: 700;
|
||||
color: var(--coz-fg-dim);
|
||||
letter-spacing: 1.2px;
|
||||
}
|
||||
|
||||
&[role~="menuitem"] {
|
||||
min-height: 32px !important;
|
||||
}
|
||||
|
||||
&[role~="presentation"] {
|
||||
height: 1px !important;
|
||||
margin: 4px 0;
|
||||
background-color: var(--coz-stroke-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
130
frontend/packages/project-ide/main/src/index.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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 'reflect-metadata';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import React, { useMemo, memo } from 'react';
|
||||
|
||||
import { SecondarySidebar } from '@coze-project-ide/ui-adapter';
|
||||
import {
|
||||
ProjectIDEClient,
|
||||
IDEGlobalProvider,
|
||||
type ProjectIDEWidget,
|
||||
} from '@coze-project-ide/framework';
|
||||
import {
|
||||
WorkflowWidgetRegistry,
|
||||
ConversationRegistry,
|
||||
} from '@coze-project-ide/biz-workflow';
|
||||
import { PluginWidgetRegistry } from '@coze-project-ide/biz-plugin-registry-adapter';
|
||||
import {
|
||||
KnowledgeWidgetRegistry,
|
||||
VariablesWidgetRegistry,
|
||||
DatabaseWidgetRegistry,
|
||||
} from '@coze-project-ide/biz-data/registry';
|
||||
import { createResourceFolderPlugin } from '@coze-project-ide/biz-components';
|
||||
import { useProjectAuth, EProjectPermission } from '@coze-common/auth';
|
||||
|
||||
import { createAppPlugin } from './plugins';
|
||||
import IDELayout from './layout';
|
||||
import {
|
||||
TopBar,
|
||||
PrimarySidebar,
|
||||
widgetTitleRender,
|
||||
WidgetDefaultRenderer,
|
||||
SidebarExpand,
|
||||
ToolBar,
|
||||
GlobalModals,
|
||||
ErrorFallback,
|
||||
GlobalHandler,
|
||||
BrowserTitle,
|
||||
GlobalLoading,
|
||||
Configuration,
|
||||
UIBuilder,
|
||||
} from './components';
|
||||
|
||||
import './styles/recommend.css';
|
||||
import './index.less';
|
||||
|
||||
interface ProjectIDEProps {
|
||||
spaceId: string;
|
||||
projectId: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
const ProjectIDE: React.FC<ProjectIDEProps> = memo(
|
||||
({ spaceId, projectId, version }) => {
|
||||
const navigate = useNavigate();
|
||||
const canView = useProjectAuth(EProjectPermission.View, projectId, spaceId);
|
||||
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
view: {
|
||||
widgetRegistries: [
|
||||
// The community version does not currently support conversation management in project, for future expansion
|
||||
...(IS_OPEN_SOURCE ? [] : [ConversationRegistry]),
|
||||
WorkflowWidgetRegistry,
|
||||
DatabaseWidgetRegistry,
|
||||
KnowledgeWidgetRegistry,
|
||||
PluginWidgetRegistry,
|
||||
VariablesWidgetRegistry,
|
||||
],
|
||||
secondarySidebar: SecondarySidebar,
|
||||
topBar: TopBar,
|
||||
primarySideBar: PrimarySidebar as () => React.ReactElement<any, any>,
|
||||
configuration: Configuration,
|
||||
widgetTitleRender,
|
||||
widgetDefaultRender: WidgetDefaultRenderer,
|
||||
widgetFallbackRender: ({ widget }) => (
|
||||
// <div>Widget error: {widget.id}</div>
|
||||
<ErrorFallback />
|
||||
),
|
||||
preToolbar: () => <SidebarExpand />,
|
||||
toolbar: (widget: ProjectIDEWidget) => <ToolBar widget={widget} />,
|
||||
uiBuilder: () => (IS_OVERSEA ? null : <UIBuilder />),
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const plugins = useMemo(
|
||||
() => [
|
||||
createAppPlugin({ spaceId, projectId, navigate, version }),
|
||||
createResourceFolderPlugin(),
|
||||
],
|
||||
[spaceId, projectId, version, navigate],
|
||||
);
|
||||
if (!canView) {
|
||||
// 无法查看跳转到兜底报错页
|
||||
throw new Error('can not view');
|
||||
}
|
||||
|
||||
return (
|
||||
<IDEGlobalProvider
|
||||
spaceId={spaceId}
|
||||
projectId={projectId}
|
||||
version={version}
|
||||
>
|
||||
<ProjectIDEClient presetOptions={options} plugins={plugins}>
|
||||
<BrowserTitle />
|
||||
<GlobalModals />
|
||||
<GlobalHandler spaceId={spaceId} projectId={projectId} />
|
||||
<GlobalLoading />
|
||||
</ProjectIDEClient>
|
||||
</IDEGlobalProvider>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export { ProjectIDE, IDELayout };
|
||||
64
frontend/packages/project-ide/main/src/layout.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 { useParams } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
|
||||
import { useDestoryProject } from '@coze-common/auth';
|
||||
import { useInitProjectRole } from '@coze-common/auth-adapter';
|
||||
|
||||
import { ProjectIDE } from './index';
|
||||
|
||||
const ProjectIDEContainer = ({
|
||||
spaceId,
|
||||
projectId,
|
||||
version,
|
||||
}: {
|
||||
spaceId: string;
|
||||
projectId: string;
|
||||
version: string;
|
||||
}) => {
|
||||
useDestoryProject(projectId);
|
||||
|
||||
// 初始化Project角色数据
|
||||
const isCompleted = useInitProjectRole(spaceId, projectId);
|
||||
|
||||
return isCompleted ? (
|
||||
<ProjectIDE spaceId={spaceId} projectId={projectId} version={version} />
|
||||
) : null;
|
||||
};
|
||||
|
||||
const Page = () => {
|
||||
const { space_id: spaceId, project_id: projectId } = useParams<{
|
||||
space_id: string;
|
||||
project_id: string;
|
||||
}>();
|
||||
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
const commitVersion = searchParams.get('commit_version');
|
||||
|
||||
return (
|
||||
<ProjectIDEContainer
|
||||
key={projectId}
|
||||
spaceId={spaceId || ''}
|
||||
projectId={projectId || ''}
|
||||
version={commitVersion || ''}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* project ide app 的生命周期
|
||||
*/
|
||||
import { injectable, inject } from 'inversify';
|
||||
import {
|
||||
type LifecycleContribution,
|
||||
LayoutRestorer,
|
||||
Emitter,
|
||||
} from '@coze-project-ide/framework';
|
||||
|
||||
import { WidgetEventService } from './widget-event-service';
|
||||
import { ProjectInfoService } from './project-info-service';
|
||||
import { OpenURIResourceService } from './open-url-resource-service';
|
||||
|
||||
@injectable()
|
||||
export class AppContribution implements LifecycleContribution {
|
||||
@inject(OpenURIResourceService)
|
||||
private openURIResourceService: OpenURIResourceService;
|
||||
|
||||
@inject(WidgetEventService)
|
||||
private widgetEventService: WidgetEventService;
|
||||
|
||||
@inject(LayoutRestorer)
|
||||
private layoutRestorer: LayoutRestorer;
|
||||
|
||||
@inject(ProjectInfoService)
|
||||
private projectInfoService: ProjectInfoService;
|
||||
|
||||
onStartedEmitter = new Emitter<void>();
|
||||
onStarted = this.onStartedEmitter.event;
|
||||
|
||||
// ide 初始化完成,可执行业务逻辑的时机
|
||||
onStart() {
|
||||
// 更新项目信息
|
||||
this.projectInfoService.init();
|
||||
|
||||
// // 打开 url 上携带的资源
|
||||
this.openURIResourceService.open();
|
||||
this.openURIResourceService.listen();
|
||||
// 订阅变化事件
|
||||
this.widgetEventService.listen();
|
||||
// listen layout store
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
this.layoutRestorer.listen();
|
||||
this.onStartedEmitter.fire();
|
||||
}
|
||||
|
||||
onDispose() {
|
||||
// 销毁所有的订阅
|
||||
this.widgetEventService.dispose();
|
||||
this.openURIResourceService.dispose();
|
||||
this.onStartedEmitter.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 承载 project ide app 业务逻辑的插件
|
||||
*/
|
||||
import { type NavigateFunction } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
bindContributions,
|
||||
definePluginCreator,
|
||||
LifecycleContribution,
|
||||
LayoutRestorer,
|
||||
type PluginCreator,
|
||||
OptionsService,
|
||||
} from '@coze-project-ide/framework';
|
||||
|
||||
import { WidgetEventService } from './widget-event-service';
|
||||
import { ProjectInfoService } from './project-info-service';
|
||||
import { OpenURIResourceService } from './open-url-resource-service';
|
||||
import { LayoutRestoreService } from './layout-restore-service';
|
||||
import { AppContribution } from './app-contribution';
|
||||
|
||||
interface createAppPluginOptions {
|
||||
spaceId: string;
|
||||
projectId: string;
|
||||
version: string;
|
||||
navigate: NavigateFunction;
|
||||
}
|
||||
|
||||
export const createAppPlugin: PluginCreator<createAppPluginOptions> =
|
||||
definePluginCreator({
|
||||
onBind({ bind, rebind }, options) {
|
||||
bind(OptionsService).toConstantValue(options);
|
||||
bind(ProjectInfoService).toSelf().inSingletonScope();
|
||||
|
||||
bind(OpenURIResourceService).toSelf().inSingletonScope();
|
||||
bind(WidgetEventService).toSelf().inSingletonScope();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-expect-error
|
||||
rebind(LayoutRestorer).to(LayoutRestoreService).inSingletonScope();
|
||||
|
||||
bindContributions(bind, AppContribution, [LifecycleContribution]);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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 { isPlainObject } from 'lodash-es';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import {
|
||||
ApplicationShell,
|
||||
WidgetManager,
|
||||
ViewRenderer,
|
||||
SIDEBAR_URI,
|
||||
MAIN_PANEL_DEFAULT_URI,
|
||||
URI,
|
||||
type ReactWidget,
|
||||
type ProjectIDEWidget,
|
||||
OptionsService,
|
||||
} from '@coze-project-ide/framework';
|
||||
|
||||
import { saveLayoutData, readLayoutData } from './utils/layout-store';
|
||||
|
||||
/**
|
||||
* 被持久化的 widget 可能是普通的 widget 也可能是 project 特定的 widget
|
||||
*/
|
||||
type LayoutWidget = ReactWidget | ProjectIDEWidget;
|
||||
|
||||
interface LayoutWidgetData {
|
||||
uri: string;
|
||||
title?: string;
|
||||
iconType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是 ProjectIDEWidget
|
||||
*/
|
||||
const isProjectIDEWidget = (w: LayoutWidget): w is ProjectIDEWidget =>
|
||||
!!(w as ProjectIDEWidget).context;
|
||||
|
||||
@injectable()
|
||||
export class LayoutRestoreService {
|
||||
@inject(ApplicationShell)
|
||||
private applicationShell: ApplicationShell;
|
||||
@inject(WidgetManager)
|
||||
protected readonly widgetManager: WidgetManager;
|
||||
@inject(ViewRenderer)
|
||||
protected readonly viewRenderer: ViewRenderer;
|
||||
|
||||
@inject(OptionsService)
|
||||
private optionsService: OptionsService;
|
||||
|
||||
/**
|
||||
* 本地数据是否生效
|
||||
*/
|
||||
private _openFirstWorkflow = false;
|
||||
|
||||
/**
|
||||
* 是否启用持久化,暂时不可配置,若开发过程中出现问题,可以关闭
|
||||
* 此开关只会开关是否在初始化时恢复布局数据
|
||||
*/
|
||||
private enabled = true;
|
||||
// private enabled = false;
|
||||
|
||||
get openFirstWorkflow() {
|
||||
return this._openFirstWorkflow;
|
||||
}
|
||||
|
||||
set openFirstWorkflow(status: boolean) {
|
||||
this._openFirstWorkflow = status;
|
||||
}
|
||||
|
||||
init() {
|
||||
//
|
||||
}
|
||||
storeLayout() {
|
||||
saveLayoutData(
|
||||
this.optionsService.spaceId,
|
||||
this.optionsService.projectId,
|
||||
this.getLayoutData(),
|
||||
);
|
||||
}
|
||||
async restoreLayout() {
|
||||
// 无论是否需要持久化,这一步必须要做
|
||||
await this.addSidebarWidget();
|
||||
if (this.enabled) {
|
||||
const data = await readLayoutData(
|
||||
this.optionsService.spaceId,
|
||||
this.optionsService.projectId,
|
||||
);
|
||||
await this.setLayoutData(data || {});
|
||||
}
|
||||
}
|
||||
storeWidget() {
|
||||
//
|
||||
}
|
||||
restoreWidget() {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成当前 ide 的布局数据
|
||||
*/
|
||||
getLayoutData() {
|
||||
const data: Record<string, any> = {};
|
||||
const { primarySidebar, mainPanel } = this.applicationShell;
|
||||
|
||||
/**
|
||||
* primarySidebar 数据
|
||||
* 在当前特化业务下,primarySidebar 只可能打开特定的 widget,所以这里无需存储通用的 widgets 数据
|
||||
*/
|
||||
data.primarySidebar = {
|
||||
isHidden: !!primarySidebar?.isHidden,
|
||||
};
|
||||
|
||||
const mainPanelData = mainPanel.saveLayout();
|
||||
data.mainPanel = this.widgetsStringifyBFS(mainPanelData);
|
||||
|
||||
return data;
|
||||
}
|
||||
async setLayoutData(data) {
|
||||
const { primarySidebar, mainPanel } = data || {};
|
||||
|
||||
/**
|
||||
* primarySidebar 面板初始化
|
||||
* 1. 数据不存在时,说明没有本地数据,需要默认打开
|
||||
* 2. 数据存在,且 hidden 为假,默认打开
|
||||
* 3. 其他情况不打开
|
||||
*/
|
||||
if (!primarySidebar || !primarySidebar.isHidden) {
|
||||
this.applicationShell.primarySidebar.show();
|
||||
} else {
|
||||
this.applicationShell.primarySidebar.hide();
|
||||
}
|
||||
|
||||
if (mainPanel) {
|
||||
const mainPanelData = await this.widgetsParseBFS(mainPanel);
|
||||
// 如果初始化的时候没有 widget 打开,默认打开一个。
|
||||
// widget: tab
|
||||
// children: 分屏
|
||||
const { main } = mainPanelData || {};
|
||||
if (!main?.widgets?.length && !main?.children?.length) {
|
||||
this._openFirstWorkflow = true;
|
||||
}
|
||||
this.applicationShell.mainPanel.restoreLayout(mainPanelData);
|
||||
// FlowDockPanel 需要挂载监听
|
||||
this.applicationShell.mainPanel.initWidgets();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 挂载默认的 sidebar widget
|
||||
*/
|
||||
async addSidebarWidget() {
|
||||
const widget = await this.widgetParse({
|
||||
uri: SIDEBAR_URI.toString(),
|
||||
});
|
||||
this.applicationShell.primarySidebar.addWidget(widget);
|
||||
}
|
||||
|
||||
listen() {
|
||||
'{"primarySidebar":{"isHidden":false},"mainPanel":{"main":{"type":"tab-area","widgets":[{"uri":"coze-project:///workflow/7446703015509245996","title":"project_more_version","iconType":"0"},{"uri":"coze-project:///plugin/7446710920656896044","title":"头条新闻forautotest"},{"uri":"coze-project:///workflow/7446703015509377068","title":"wl_pro_to_pro_use_library_plug_462971","iconType":"0"},{"uri":"coze-project:///workflow/7446703015509311532","title":"wl_pro_to_library","iconType":"0"},{"uri":"coze-project:///workflow/7446703015509344300","title":"wl_pro_to_pro_use_library_plug","iconType":"0"}],"currentIndex":1}}}';
|
||||
|
||||
const listener = () => {
|
||||
this.storeLayout();
|
||||
window.removeEventListener('unload', listener);
|
||||
};
|
||||
window.addEventListener('unload', listener);
|
||||
}
|
||||
|
||||
private widgetStringify(widget: LayoutWidget) {
|
||||
if (!widget?.uri) {
|
||||
return;
|
||||
}
|
||||
const data: LayoutWidgetData = {
|
||||
uri: widget.uri.toString(),
|
||||
};
|
||||
|
||||
if (isProjectIDEWidget(widget)) {
|
||||
const sub = widget.context.widget;
|
||||
const title = sub.getTitle();
|
||||
if (title) {
|
||||
data.title = title;
|
||||
}
|
||||
const iconType = sub.getIconType();
|
||||
if (iconType) {
|
||||
data.iconType = iconType;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
private widgetsStringify(widgets: LayoutWidget[]) {
|
||||
return widgets
|
||||
.map(widget => this.widgetStringify(widget))
|
||||
.filter(str => str?.uri && str.uri !== MAIN_PANEL_DEFAULT_URI.toString());
|
||||
}
|
||||
private widgetsStringifyBFS(data: any) {
|
||||
const bfs = next => {
|
||||
if (isPlainObject(next)) {
|
||||
return Object.keys(next).reduce((acc, key) => {
|
||||
if (key === 'widgets' && Array.isArray(next[key])) {
|
||||
acc[key] = this.widgetsStringify(next[key]);
|
||||
} else {
|
||||
acc[key] = bfs(next[key]);
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
} else if (Array.isArray(next)) {
|
||||
return next.map(bfs);
|
||||
}
|
||||
return next;
|
||||
};
|
||||
return bfs(data);
|
||||
}
|
||||
private async widgetParse(data) {
|
||||
const uri = new URI(data.uri);
|
||||
const factory = this.widgetManager.getFactoryFromURI(uri);
|
||||
const widget = (await this.widgetManager.getOrCreateWidgetFromURI(
|
||||
uri,
|
||||
factory,
|
||||
)) as LayoutWidget;
|
||||
if (isProjectIDEWidget(widget)) {
|
||||
const sub = widget.context.widget;
|
||||
data.title && sub.setTitle(data.title, 'normal');
|
||||
data.iconType && sub.setIconType(data.iconType);
|
||||
}
|
||||
this.viewRenderer.addReactPortal(widget);
|
||||
|
||||
this.applicationShell.track(widget);
|
||||
return widget;
|
||||
}
|
||||
private async widgetsParseBFS(data) {
|
||||
const bfs = async next => {
|
||||
if (isPlainObject(next)) {
|
||||
return await Object.keys(next).reduce(async (accPromise, key) => {
|
||||
const acc = await accPromise;
|
||||
if (key === 'widgets' && Array.isArray(next[key])) {
|
||||
acc[key] = await Promise.all(
|
||||
next[key].map(w => this.widgetParse(w)),
|
||||
);
|
||||
} else {
|
||||
acc[key] = await bfs(next[key]);
|
||||
}
|
||||
return acc;
|
||||
}, Promise.resolve({}));
|
||||
} else if (Array.isArray(next)) {
|
||||
return await Promise.all(next.map(bfs));
|
||||
}
|
||||
return next;
|
||||
};
|
||||
return await bfs(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* project ide app 初始化时打开 url 上携带的资源
|
||||
*/
|
||||
import { inject, injectable } from 'inversify';
|
||||
import {
|
||||
getURIByPath,
|
||||
getResourceByPathname,
|
||||
getURIPathByPathname,
|
||||
getURIByResource,
|
||||
ProjectIDEServices,
|
||||
ViewService,
|
||||
Disposable,
|
||||
UI_BUILDER_URI,
|
||||
MAIN_PANEL_DEFAULT_URI,
|
||||
DisposableCollection,
|
||||
ApplicationShell,
|
||||
type ReactWidget,
|
||||
} from '@coze-project-ide/framework';
|
||||
|
||||
@injectable()
|
||||
export class OpenURIResourceService {
|
||||
@inject(ProjectIDEServices)
|
||||
private projectIDEServices: ProjectIDEServices;
|
||||
|
||||
@inject(ViewService)
|
||||
private viewService: ViewService;
|
||||
|
||||
@inject(ApplicationShell)
|
||||
private applicationShell: ApplicationShell;
|
||||
|
||||
private disposable = new DisposableCollection();
|
||||
|
||||
/**
|
||||
* 针对 1.直接打开;2.外部系统跳转的场景,请勿在此添加其他副作用逻辑
|
||||
*/
|
||||
open() {
|
||||
const { resourceType } = getResourceByPathname(window.location.pathname);
|
||||
// ui-builder
|
||||
if (resourceType === UI_BUILDER_URI.displayName) {
|
||||
this.openDesign();
|
||||
// 展示默认页
|
||||
this.tryOpenDefault();
|
||||
} else {
|
||||
const path = getURIPathByPathname(window.location.pathname);
|
||||
if (!path || path.startsWith(MAIN_PANEL_DEFAULT_URI.displayName)) {
|
||||
this.tryOpenDefault();
|
||||
// 路由不匹配时需要手动激活 currentWidget
|
||||
if (this.applicationShell.mainPanel.currentTitle?.owner) {
|
||||
this.applicationShell.setCurrentWidget(
|
||||
this.applicationShell.mainPanel?.currentTitle?.owner as ReactWidget,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.projectIDEServices.view.open(getURIByPath(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listen() {
|
||||
const POP_STATE_EVENT_TYPE = 'popstate';
|
||||
window.addEventListener(POP_STATE_EVENT_TYPE, this.syncPopstate);
|
||||
this.disposable.push(
|
||||
Disposable.create(() =>
|
||||
window.removeEventListener(POP_STATE_EVENT_TYPE, this.syncPopstate),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
openDevelop(resourceType: string, resourceId: string, query?: string) {
|
||||
this.projectIDEServices.view.open(
|
||||
getURIByResource(resourceType, resourceId, query),
|
||||
);
|
||||
}
|
||||
|
||||
openDesign() {
|
||||
this.projectIDEServices.view.openPanel('ui-builder');
|
||||
}
|
||||
tryOpenDefault() {
|
||||
if (this.viewService.shell.mainPanel?.tabBars?.()?.next?.()?.done) {
|
||||
this.projectIDEServices.view.openDefault();
|
||||
}
|
||||
}
|
||||
|
||||
syncPopstate = () => {
|
||||
this.open();
|
||||
};
|
||||
dispose() {
|
||||
this.disposable.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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 { inject, injectable } from 'inversify';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { type ProjectFormValues } from '@coze-studio/project-entity-adapter';
|
||||
import {
|
||||
Emitter,
|
||||
type Event,
|
||||
ModalService,
|
||||
OptionsService,
|
||||
ModalType,
|
||||
ErrorService,
|
||||
} from '@coze-project-ide/framework';
|
||||
import {
|
||||
IntelligenceType,
|
||||
type IntelligenceBasicInfo,
|
||||
type IntelligencePublishInfo,
|
||||
type User,
|
||||
} from '@coze-arch/idl/intelligence_api';
|
||||
import {
|
||||
BehaviorType,
|
||||
SpaceResourceType,
|
||||
} from '@coze-arch/bot-api/playground_api';
|
||||
import {
|
||||
PlaygroundApi,
|
||||
PluginDevelopApi,
|
||||
intelligenceApi,
|
||||
} from '@coze-arch/bot-api';
|
||||
|
||||
@injectable()
|
||||
export class ProjectInfoService {
|
||||
@inject(OptionsService)
|
||||
private optionsService: OptionsService;
|
||||
|
||||
@inject(ModalService)
|
||||
private modalService: ModalService;
|
||||
|
||||
@inject(ErrorService)
|
||||
private errorService: ErrorService;
|
||||
|
||||
public projectInfo?: {
|
||||
projectInfo?: IntelligenceBasicInfo;
|
||||
publishInfo?: IntelligencePublishInfo;
|
||||
ownerInfo?: User;
|
||||
};
|
||||
|
||||
public initialValue: ProjectFormValues;
|
||||
|
||||
private readonly onProjectInfoUpdatedEmitter = new Emitter<void>();
|
||||
readonly onProjectInfoUpdated: Event<void> =
|
||||
this.onProjectInfoUpdatedEmitter.event;
|
||||
|
||||
init() {
|
||||
this.updateProjectInfo().catch(() => {
|
||||
// project 信息接口报错跳转到兜底页
|
||||
this.errorService.toErrorPage();
|
||||
});
|
||||
if (!IS_OPEN_SOURCE) {
|
||||
this.wakeUpPlugin();
|
||||
}
|
||||
this.initTaskList();
|
||||
this.reportUserBehavior();
|
||||
}
|
||||
|
||||
async initTaskList() {
|
||||
const res = await intelligenceApi.DraftProjectInnerTaskList({
|
||||
project_id: this.optionsService.projectId,
|
||||
});
|
||||
// 和后端确认,默认 task_list 长度为 1.
|
||||
// 如果有长度为 2 没有都住的场景,用户刷新后也可以获取到下一个。
|
||||
const { task_list } = res.data || {};
|
||||
const taskId = task_list?.[0]?.task_id;
|
||||
if (taskId) {
|
||||
// 请求轮询接口获取基础信息
|
||||
const { task_detail } = await PluginDevelopApi.ResourceCopyDetail({
|
||||
task_id: taskId,
|
||||
});
|
||||
this.modalService.onModalVisibleChangeEmitter.fire({
|
||||
type: ModalType.RESOURCE,
|
||||
scene: task_detail?.scene,
|
||||
resourceName: task_detail?.res_name,
|
||||
});
|
||||
this.modalService.doPolling(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开 project 页面需要上报,后端才能筛选出最近打开
|
||||
*/
|
||||
reportUserBehavior() {
|
||||
PlaygroundApi.ReportUserBehavior({
|
||||
space_id: this.optionsService.spaceId,
|
||||
behavior_type: BehaviorType.Visit,
|
||||
resource_id: this.optionsService.projectId,
|
||||
resource_type: SpaceResourceType.Project,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 单向请求接口
|
||||
* 提前唤醒 ide 插件,无需消费返回值
|
||||
*/
|
||||
wakeUpPlugin() {
|
||||
PluginDevelopApi.WakeupIdePlugin({
|
||||
space_id: this.optionsService.spaceId,
|
||||
project_id: this.optionsService.projectId,
|
||||
dev_id: userStoreService.getUserInfo()?.user_id_str,
|
||||
});
|
||||
}
|
||||
|
||||
async updateProjectInfo() {
|
||||
const res = await intelligenceApi.GetDraftIntelligenceInfo({
|
||||
intelligence_id: this.optionsService.projectId,
|
||||
intelligence_type: IntelligenceType.Project,
|
||||
version: this.optionsService.version || undefined,
|
||||
});
|
||||
this.projectInfo = {
|
||||
projectInfo: res.data?.basic_info,
|
||||
publishInfo: res.data?.publish_info,
|
||||
ownerInfo: res.data?.owner_info,
|
||||
};
|
||||
this.initialValue = {
|
||||
space_id: this.optionsService?.spaceId,
|
||||
project_id: this.optionsService?.projectId,
|
||||
name: this.projectInfo?.projectInfo?.name,
|
||||
description: this.projectInfo?.projectInfo?.description,
|
||||
icon_uri: [
|
||||
{
|
||||
uid: this.projectInfo?.projectInfo?.icon_uri || '',
|
||||
url: this.projectInfo?.projectInfo?.icon_url || '',
|
||||
},
|
||||
],
|
||||
};
|
||||
this.onProjectInfoUpdatedEmitter.fire();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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 { Dexie, type EntityTable } from 'dexie';
|
||||
|
||||
/**
|
||||
* 布局数据
|
||||
*/
|
||||
interface DBLayoutRow {
|
||||
/**
|
||||
* 自增 id
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* 空间 id
|
||||
*/
|
||||
spaceId: string;
|
||||
/**
|
||||
* 项目 id
|
||||
*/
|
||||
projectId: string;
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
timestamp: number;
|
||||
/**
|
||||
* 数据版本
|
||||
*/
|
||||
version: number;
|
||||
/**
|
||||
* 数据
|
||||
*/
|
||||
data: string;
|
||||
}
|
||||
|
||||
type DBLayout = Dexie & {
|
||||
layout: EntityTable<DBLayoutRow, 'id'>;
|
||||
};
|
||||
|
||||
/**
|
||||
* 持久化储存形式的版本号
|
||||
*/
|
||||
const VERSION = 3;
|
||||
|
||||
/**
|
||||
* 数据库名称
|
||||
*/
|
||||
const DB_NAME = 'CozProjectIDELayoutData';
|
||||
/**
|
||||
* 数据库版本
|
||||
*/
|
||||
const DB_VERSION = 1;
|
||||
/**
|
||||
* 数据有效期
|
||||
*/
|
||||
const DB_EXPIRE = 1000 * 60 * 60 * 24 * 30;
|
||||
|
||||
let cache: DBLayout | undefined;
|
||||
|
||||
const isExpired = (row: DBLayoutRow) =>
|
||||
row.timestamp < Date.now() - DB_EXPIRE || row.version !== VERSION;
|
||||
|
||||
/**
|
||||
* 获取数据库实例
|
||||
*/
|
||||
const getDB = () => {
|
||||
if (!cache) {
|
||||
cache = new Dexie(DB_NAME) as DBLayout;
|
||||
cache.version(DB_VERSION).stores({
|
||||
layout: '++id, spaceId, projectId, timestamp, data',
|
||||
});
|
||||
}
|
||||
return cache;
|
||||
};
|
||||
|
||||
const setDataDB = async (
|
||||
spaceId: string,
|
||||
projectId: string,
|
||||
row: Omit<DBLayoutRow, 'id' | 'projectId' | 'spaceId'>,
|
||||
) => {
|
||||
const db = getDB();
|
||||
const record = await db.layout.where({ spaceId, projectId }).first();
|
||||
if (record) {
|
||||
await db.layout.update(record.id, {
|
||||
...row,
|
||||
});
|
||||
} else {
|
||||
await db.layout.add({
|
||||
spaceId,
|
||||
projectId,
|
||||
...row,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getDataDB = async (spaceId: string, projectId: string) => {
|
||||
const db = getDB();
|
||||
const record = await db.layout.where({ spaceId, projectId }).first();
|
||||
|
||||
if (!record) {
|
||||
return undefined;
|
||||
}
|
||||
if (isExpired(record)) {
|
||||
await db.layout.where({ id: record.id }).delete();
|
||||
return undefined;
|
||||
}
|
||||
return record;
|
||||
};
|
||||
|
||||
const LOCAL_STORAGE_KEY_PREFIX = 'coz-project-ide-layout-data';
|
||||
|
||||
const setDataLS = (
|
||||
spaceId: string,
|
||||
projectId: string,
|
||||
row: Omit<DBLayoutRow, 'id' | 'projectId' | 'spaceId'>,
|
||||
) => {
|
||||
const key = `${LOCAL_STORAGE_KEY_PREFIX}-${spaceId}-${projectId}`;
|
||||
window.localStorage.setItem(key, JSON.stringify(row));
|
||||
};
|
||||
|
||||
const getDataLS = (spaceId: string, projectId: string) => {
|
||||
const key = `${LOCAL_STORAGE_KEY_PREFIX}-${spaceId}-${projectId}`;
|
||||
const str = window.localStorage.getItem(key);
|
||||
if (!str) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(str);
|
||||
if (isExpired(data)) {
|
||||
window.localStorage.removeItem(key);
|
||||
return undefined;
|
||||
}
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
const deleteDataLS = (spaceId: string, projectId: string) => {
|
||||
const key = `${LOCAL_STORAGE_KEY_PREFIX}-${spaceId}-${projectId}`;
|
||||
window.localStorage.removeItem(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存布局数据
|
||||
* 注:调用时机为组件销毁或浏览器关闭时,故不可用异步函数
|
||||
*/
|
||||
const saveLayoutData = (spaceId: string, projectId: string, data: any) => {
|
||||
try {
|
||||
// 无论是什么值都需要序列化成字符串
|
||||
const str = JSON.stringify(data);
|
||||
const row = {
|
||||
data: str,
|
||||
timestamp: Number(Date.now()),
|
||||
version: VERSION,
|
||||
};
|
||||
setDataLS(spaceId, projectId, row);
|
||||
setDataDB(spaceId, projectId, row);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 读取布局数据
|
||||
* 会同时从 indexedDB 和 localStorage 中读取数据,会有以下几种情况:
|
||||
* 1. localStorage 无数据,返回 indexedDB 数据
|
||||
* 2. localStorage 有数据
|
||||
* 2.1. indexedDB 无数据,更新 indexedDB 数据,删除 localStorage 数据,返回 indexedDB 数据
|
||||
* 2.2. indexedDB 有数据,比较时间戳。返回最近的数据,删除 localStorage 数据
|
||||
* 2.2.1. 若 localStorage 数据较新,则更新到 indexedDB 中
|
||||
*/
|
||||
const readLayoutData = async (spaceId: string, projectId: string) => {
|
||||
let str;
|
||||
const recordDB = await getDataDB(spaceId, projectId);
|
||||
const recordLS = getDataLS(spaceId, projectId);
|
||||
if (!recordLS) {
|
||||
str = recordDB?.data;
|
||||
} else if (!recordDB || recordDB.timestamp < recordLS.timestamp) {
|
||||
await setDataDB(spaceId, projectId, recordLS);
|
||||
deleteDataLS(spaceId, projectId);
|
||||
str = recordLS.data;
|
||||
} else {
|
||||
deleteDataLS(spaceId, projectId);
|
||||
str = recordDB.data;
|
||||
}
|
||||
if (!str) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export { saveLayoutData, readLayoutData };
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 监听 widget 事件而需要执行的业务逻辑
|
||||
*/
|
||||
import { inject, injectable } from 'inversify';
|
||||
import {
|
||||
ProjectIDEServices,
|
||||
ViewService,
|
||||
WidgetManager,
|
||||
MAIN_PANEL_DEFAULT_URI,
|
||||
DisposableCollection,
|
||||
getPathnameByURI,
|
||||
type ReactWidget,
|
||||
OptionsService,
|
||||
compareURI,
|
||||
addPreservedSearchParams,
|
||||
} from '@coze-project-ide/framework';
|
||||
|
||||
@injectable()
|
||||
export class WidgetEventService {
|
||||
@inject(ViewService)
|
||||
private viewService: ViewService;
|
||||
|
||||
@inject(WidgetManager)
|
||||
private widgetManager: WidgetManager;
|
||||
|
||||
@inject(ProjectIDEServices)
|
||||
private projectIDEServices: ProjectIDEServices;
|
||||
|
||||
@inject(OptionsService)
|
||||
private optionsService: OptionsService;
|
||||
|
||||
private disposable = new DisposableCollection();
|
||||
|
||||
listen() {
|
||||
// listen current widget change
|
||||
this.disposable.push(
|
||||
this.viewService.shell.onCurrentWidgetChange(widget => {
|
||||
this.toggleDefaultWidget(widget);
|
||||
this.syncURL(widget);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. 有 widget 打开时需要关闭默认页
|
||||
* 2. 关闭所有 widget 时需要打开默认页
|
||||
*/
|
||||
toggleDefaultWidget(widget) {
|
||||
if ((widget as ReactWidget)?.uri) {
|
||||
const widgetUri = widget?.uri;
|
||||
if (widgetUri.displayName !== 'default') {
|
||||
// 关闭默认的 widget
|
||||
const defaultWidget = this.widgetManager.getWidgetFromURI(
|
||||
MAIN_PANEL_DEFAULT_URI,
|
||||
);
|
||||
defaultWidget?.dispose?.();
|
||||
}
|
||||
} else {
|
||||
this.viewService.disableFullScreenMode();
|
||||
this.projectIDEServices.view.openDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步切换资源 tab 时的 url 变化
|
||||
*/
|
||||
syncURL(widget) {
|
||||
if (widget) {
|
||||
const widgetUri = widget?.uri;
|
||||
// 默认页无需同步 url
|
||||
if (compareURI(widgetUri, MAIN_PANEL_DEFAULT_URI)) {
|
||||
return;
|
||||
}
|
||||
if (widgetUri) {
|
||||
const path = getPathnameByURI(widgetUri);
|
||||
if (path) {
|
||||
let url = `/space/${this.optionsService.spaceId}/project-ide/${this.optionsService.projectId}${path}`;
|
||||
if (widgetUri.query) {
|
||||
url += `?${widgetUri.query}`;
|
||||
}
|
||||
this.navigate(url);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.navigate(
|
||||
`/space/${this.optionsService.spaceId}/project-ide/${this.optionsService.projectId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
diffPath(next: string) {
|
||||
const { pathname, search } = window.location;
|
||||
return pathname + search !== next;
|
||||
}
|
||||
|
||||
navigate(url: string) {
|
||||
if (!this.diffPath(url)) {
|
||||
return;
|
||||
}
|
||||
this.optionsService.navigate(addPreservedSearchParams(url));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposable.dispose();
|
||||
}
|
||||
}
|
||||
17
frontend/packages/project-ide/main/src/plugins/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { createAppPlugin } from './create-app-plugin';
|
||||
94
frontend/packages/project-ide/main/src/styles/recommend.css
Normal file
@@ -0,0 +1,94 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
|
||||
@import './widgets.css';
|
||||
|
||||
.lm-DockPanel-overlay {
|
||||
background: rgba(255, 255, 255, 60%);
|
||||
border: 1px dashed black;
|
||||
|
||||
transition-timing-function: ease;
|
||||
transition-duration: 150ms;
|
||||
transition-property: top, left, right, bottom;
|
||||
}
|
||||
|
||||
.lm-TabBar {
|
||||
min-height: 24px;
|
||||
max-height: 24px;
|
||||
}
|
||||
|
||||
.lm-TabBar-content {
|
||||
align-items: flex-end;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
border-bottom: 1px solid #c0c0c0;
|
||||
}
|
||||
|
||||
.lm-TabBar-tab {
|
||||
flex: 0 1 125px;
|
||||
|
||||
min-width: 35px;
|
||||
min-height: 20px;
|
||||
max-height: 20px;
|
||||
margin-left: -1px;
|
||||
padding: 0 10px;
|
||||
|
||||
font:
|
||||
12px Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
line-height: 20px;
|
||||
|
||||
background: #e5e5e5;
|
||||
border: 1px solid #c0c0c0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.lm-TabBar-tabLabel .lm-TabBar-tabInput {
|
||||
padding: 0;
|
||||
font:
|
||||
12px Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.lm-TabBar-tab.lm-mod-current {
|
||||
transform: translateY(1px);
|
||||
min-height: 23px;
|
||||
max-height: 23px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.lm-TabBar-tab:hover:not(.lm-mod-current) {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.lm-TabBar-tab:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.lm-TabBar-tabIcon,
|
||||
.lm-TabBar-tabLabel,
|
||||
.lm-TabBar-tabCloseIcon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.lm-TabBar-tab.lm-mod-closable > .lm-TabBar-tabCloseIcon {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.lm-TabBar .lm-TabBar-addButton {
|
||||
padding: 0 6px;
|
||||
border-bottom: 1px solid #c0c0c0;
|
||||
}
|
||||
|
||||
.lm-TabBar-tab.lm-mod-drag-image {
|
||||
min-width: 125px;
|
||||
min-height: 23px;
|
||||
max-height: 23px;
|
||||
|
||||
border: none;
|
||||
box-shadow: 1px 1px 2px rgba(0, 0, 0, 30%);
|
||||
}
|
||||
222
frontend/packages/project-ide/main/src/styles/widgets.css
Normal file
@@ -0,0 +1,222 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
/* stylelint-disable selector-pseudo-element-colon-notation */
|
||||
|
||||
.lm-Widget {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.lm-Widget.lm-mod-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.lm-DockPanel {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.lm-DockPanel-widget {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.lm-DockPanel-tabBar {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.lm-DockPanel-handle {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.lm-DockPanel-handle.lm-mod-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.lm-DockPanel-handle:after {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.lm-DockPanel-handle[data-orientation='horizontal'] {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.lm-DockPanel-handle[data-orientation='vertical'] {
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.lm-DockPanel-handle[data-orientation='horizontal']:after {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
min-width: 8px;
|
||||
}
|
||||
|
||||
.lm-DockPanel-handle[data-orientation='vertical']:after {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
min-height: 8px;
|
||||
}
|
||||
|
||||
.lm-DockPanel-overlay {
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.lm-DockPanel-overlay.lm-mod-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.lm-SplitPanel-child {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.lm-SplitPanel-handle {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.lm-SplitPanel-handle.lm-mod-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.lm-SplitPanel-handle:after {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.lm-SplitPanel[data-orientation='horizontal'] > .lm-SplitPanel-handle {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.lm-SplitPanel[data-orientation='vertical'] > .lm-SplitPanel-handle {
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.lm-SplitPanel[data-orientation='horizontal'] > .lm-SplitPanel-handle:after {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
min-width: 8px;
|
||||
}
|
||||
|
||||
.lm-SplitPanel[data-orientation='vertical'] > .lm-SplitPanel-handle:after {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
min-height: 8px;
|
||||
}
|
||||
|
||||
|
||||
.lm-TabBar {
|
||||
user-select: none;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.lm-TabBar[data-orientation='horizontal'] {
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.lm-TabBar[data-orientation='vertical'] {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.lm-TabBar-content {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.lm-TabBar[data-orientation='horizontal'] > .lm-TabBar-content {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.lm-TabBar[data-orientation='vertical'] > .lm-TabBar-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.lm-TabBar-tab {
|
||||
touch-action: none; /* Disable native Drag/Drop */
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.lm-TabBar-tabIcon,
|
||||
.lm-TabBar-tabCloseIcon {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.lm-TabBar-tabLabel {
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lm-TabBar-tabInput {
|
||||
user-select: all;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lm-TabBar-tab.lm-mod-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.lm-TabBar-addButton.lm-mod-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.lm-TabBar.lm-mod-dragging .lm-TabBar-tab {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.lm-TabBar.lm-mod-dragging[data-orientation='horizontal'] .lm-TabBar-tab {
|
||||
left: 0;
|
||||
transition: left 150ms ease;
|
||||
}
|
||||
|
||||
.lm-TabBar.lm-mod-dragging[data-orientation='vertical'] .lm-TabBar-tab {
|
||||
top: 0;
|
||||
transition: top 150ms ease;
|
||||
}
|
||||
|
||||
.lm-TabBar.lm-mod-dragging .lm-TabBar-tab.lm-mod-dragging {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.lm-TabBar-tabLabel .lm-TabBar-tabInput {
|
||||
user-select: all;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
|
||||
.lm-TabPanel-tabBar {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.lm-TabPanel-stackedPanel {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||