feat: manually mirror opencoze's code from bytedance

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

View File

@@ -0,0 +1,401 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable max-lines-per-function */
/* eslint @coze-arch/max-line-per-function: ["error", {"max": 500}] */
/* eslint-disable complexity */
import { type FC, useEffect } from 'react';
import classNames from 'classnames';
import {
highlightFilterStyle,
WorkspaceEmpty,
DevelopCustomPublishStatus,
isPublishStatus,
isRecentOpen,
isSearchScopeEnum,
getPublishRequestParam,
getTypeRequestParams,
isEqualDefaultFilterParams,
isFilterHighlight,
CREATOR_FILTER_OPTIONS,
FILTER_PARAMS_DEFAULT,
STATUS_FILTER_OPTIONS,
TYPE_FILTER_OPTIONS,
BotCard,
Content,
Header,
HeaderActions,
HeaderTitle,
Layout,
SubHeader,
SubHeaderFilters,
SubHeaderSearch,
useIntelligenceList,
useIntelligenceActions,
useCachedQueryParams,
useGlobalEventListeners,
type DevelopProps,
useProjectCopyPolling,
useCardActions,
} from '@coze-studio/workspace-base/develop';
import { useSpaceStore } from '@coze-foundation/space-store-adapter';
import {
IntelligenceType,
search,
SearchScope,
} from '@coze-arch/idl/intelligence_api';
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
import { IconCozLoading, IconCozPlus } from '@coze-arch/coze-design/icons';
import {
Button,
IconButton,
Search,
Select,
Spin,
} from '@coze-arch/coze-design';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import { SpaceType } from '@coze-arch/bot-api/developer_api';
export const Develop: FC<DevelopProps> = ({ spaceId }) => {
const isPersonal = useSpaceStore(
state => state.space.space_type === SpaceType.Personal,
);
// 关键字检索 & 筛选
const [filterParams, setFilterParams, debouncedSetSearchValue] =
useCachedQueryParams();
const {
isIntelligenceTypeFilterHighlight,
isOwnerFilterHighlight,
isPublishAndOpenFilterHighlight,
} = isFilterHighlight(filterParams);
const {
listResp: { loading, data, loadingMore, mutate, noMore, reload },
containerRef,
} = useIntelligenceList({
params: {
spaceId,
searchValue: filterParams.searchValue,
types: getTypeRequestParams({
type: filterParams.searchType,
}),
hasPublished: getPublishRequestParam(filterParams.isPublish),
recentlyOpen: filterParams.recentlyOpen,
searchScope: filterParams.searchScope,
// 固定值,来自历史代码
orderBy: filterParams.isPublish
? search.OrderBy.PublishTime
: search.OrderBy.UpdateTime,
},
});
useGlobalEventListeners({ reload, spaceId });
useEffect(() => {
setFilterParams(prev => ({
...prev,
searchValue: '',
}));
}, [spaceId]);
/**
* report tea event
*/
useEffect(() => {
sendTeaEvent(EVENT_NAMES.view_bot, { tab: 'my_bots' });
}, []);
useProjectCopyPolling({
listData: data?.list,
spaceId,
mutate,
});
const { contextHolder: cardActionsContextHolder, actions: cardActions } =
useCardActions({
isPersonalSpace: isPersonal,
mutate,
});
/**
* 创建 project
*/
const { contextHolder, actions } = useIntelligenceActions({
spaceId,
mutateList: mutate,
reloadList: reload,
});
return (
<>
{contextHolder}
{cardActionsContextHolder}
<Layout>
<Header>
<HeaderTitle>
<span>{I18n.t('workspace_develop')}</span>
</HeaderTitle>
<HeaderActions>
<Button icon={<IconCozPlus />} onClick={actions.createIntelligence}>
{I18n.t('workspace_create')}
</Button>
</HeaderActions>
</Header>
<SubHeader>
<SubHeaderFilters>
<Select
className="min-w-[128px]"
style={
isIntelligenceTypeFilterHighlight ? highlightFilterStyle : {}
}
value={filterParams.searchType}
onChange={val => {
setFilterParams(prev => ({
...prev,
searchType:
val as (typeof TYPE_FILTER_OPTIONS)[number]['value'],
}));
// tea 埋点
sendTeaEvent(EVENT_NAMES.workspace_action_front, {
space_id: spaceId,
space_type: isPersonal ? 'personal' : 'teamspace',
tab_name: 'develop',
action: 'filter',
filter_type: 'types',
filter_name: I18n.t(
TYPE_FILTER_OPTIONS.find(opt => opt.value === val)
?.labelI18NKey as I18nKeysNoOptionsType,
),
});
}}
>
{TYPE_FILTER_OPTIONS.map(opt => (
<Select.Option key={opt.value} value={opt.value}>
{I18n.t(opt.labelI18NKey)}
</Select.Option>
))}
</Select>
{!isPersonal ? (
/**
* Search Scope
* 所有人
* 由我创建
*/
<Select
className="min-w-[128px]"
style={isOwnerFilterHighlight ? highlightFilterStyle : {}}
value={filterParams.searchScope}
onChange={val => {
if (!isSearchScopeEnum(val)) {
return;
}
setFilterParams(p => {
if (val === SearchScope.CreateByMe && p.recentlyOpen) {
return {
...p,
recentlyOpen: false,
isPublish: DevelopCustomPublishStatus.All,
searchScope: val,
};
}
return {
...p,
searchScope: val,
};
});
// tea 埋点
sendTeaEvent(EVENT_NAMES.workspace_action_front, {
space_id: spaceId,
space_type: isPersonal ? 'personal' : 'teamspace',
tab_name: 'develop',
action: 'filter',
filter_type: 'creators',
filter_name: I18n.t(
CREATOR_FILTER_OPTIONS.find(opt => opt.value === val)
?.labelI18NKey as I18nKeysNoOptionsType,
),
});
}}
>
{CREATOR_FILTER_OPTIONS.map(opt => (
<Select.Option key={opt.value} value={opt.value}>
{I18n.t(opt.labelI18NKey)}
</Select.Option>
))}
</Select>
) : null}
{/*
全部
已发布
最近打开
*/}
<Select
className="min-w-[128px]"
style={
isPublishAndOpenFilterHighlight ? highlightFilterStyle : {}
}
value={
filterParams.recentlyOpen
? 'recentOpened'
: filterParams.isPublish
}
onChange={val => {
setFilterParams(p => ({
...p,
searchScope: SearchScope.All,
recentlyOpen: isRecentOpen(val),
isPublish: isPublishStatus(val)
? val
: DevelopCustomPublishStatus.All,
}));
// tea 埋点
sendTeaEvent(EVENT_NAMES.workspace_action_front, {
space_id: spaceId,
space_type: isPersonal ? 'personal' : 'teamspace',
tab_name: 'develop',
action: 'filter',
filter_type: 'status',
filter_name: I18n.t(
STATUS_FILTER_OPTIONS.find(opt => opt.value === val)
?.labelI18NKey as I18nKeysNoOptionsType,
),
});
}}
>
{STATUS_FILTER_OPTIONS.map(opt => (
<Select.Option key={opt.value} value={opt.value}>
{I18n.t(opt.labelI18NKey)}
</Select.Option>
))}
</Select>
</SubHeaderFilters>
<SubHeaderSearch>
<Search
disabled={filterParams.recentlyOpen}
showClear={true}
className="w-[200px]"
style={filterParams.searchValue ? highlightFilterStyle : {}}
placeholder={I18n.t('workspace_develop_search_project')}
value={filterParams.searchValue}
onChange={val => {
debouncedSetSearchValue(val);
}}
/>
</SubHeaderSearch>
</SubHeader>
<Content ref={containerRef}>
<Spin spinning={loading} wrapperClassName="w-full !h-[80vh]">
{/* 有数据时 */}
{data?.list.length ? (
<div
className={classNames(
'grid grid-cols-3 auto-rows-min gap-[20px]',
'[@media(min-width:1600px)]:grid-cols-4',
)}
>
{data.list.map((project, index) => (
<BotCard
key={`${project.basic_info?.id}-${index}`}
intelligenceInfo={project}
onRetryCopy={cardActions.onRetryCopy}
onCancelCopyAfterFailed={
cardActions.onCancelCopyAfterFailed
}
onClick={() => {
cardActions.onClick(project);
}}
onUpdateIntelligenceInfo={cardActions.onUpdate}
onDelete={({ name, id, type }) => {
if (type === IntelligenceType.Bot) {
actions.deleteIntelligence({
name,
spaceId,
agentId: id,
});
return;
}
if (type === IntelligenceType.Project) {
actions.deleteIntelligence({ name, projectId: id });
return;
}
}}
onCopyAgent={cardActions.onCopyAgent}
onCopyProject={params => {
cardActions.onCopyProject({
initialValue: {
project_id: params.id ?? '',
to_space_id: spaceId,
name: params.name ?? '',
description: params.description,
icon_uri: [
{
uid: params.icon_uri,
url: params.icon_url ?? '',
},
],
},
});
}}
timePrefixType={
filterParams.recentlyOpen
? 'recentOpen'
: filterParams.isPublish
? 'publish'
: 'edit'
}
/>
))}
</div>
) : null}
{!data?.list?.length && !loading ? (
<WorkspaceEmpty
onClear={() => {
setFilterParams(FILTER_PARAMS_DEFAULT);
}}
hasFilter={
!isEqualDefaultFilterParams({
filterParams,
})
}
/>
) : null}
{/* 展示底部的 loading */}
{data?.list.length && loadingMore ? (
<div className="flex items-center justify-center w-full h-[38px] my-[20px] coz-fg-secondary text-[12px]">
<IconButton
icon={<IconCozLoading />}
loading
color="secondary"
/>
<div>{I18n.t('Loading')}...</div>
</div>
) : null}
{/* 没有更多数据的时候要展示个占位 */}
{noMore && data?.list.length ? (
<div className="h-[38px] my-[20px]"></div>
) : null}
</Spin>
</Content>
</Layout>
</>
);
};

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC, useRef } from 'react';
import {
BaseLibraryPage,
useDatabaseConfig,
usePluginConfig,
useWorkflowConfig,
usePromptConfig,
useKnowledgeConfig,
} from '@coze-studio/workspace-base/library';
export const LibraryPage: FC<{ spaceId: string }> = ({ spaceId }) => {
const basePageRef = useRef<{ reloadList: () => void }>(null);
const configCommonParams = {
spaceId,
reloadList: () => {
basePageRef.current?.reloadList();
},
};
const { config: pluginConfig, modals: pluginModals } =
usePluginConfig(configCommonParams);
const { config: workflowConfig, modals: workflowModals } =
useWorkflowConfig(configCommonParams);
const { config: knowledgeConfig, modals: knowledgeModals } =
useKnowledgeConfig(configCommonParams);
const { config: promptConfig, modals: promptModals } =
usePromptConfig(configCommonParams);
const { config: databaseConfig, modals: databaseModals } =
useDatabaseConfig(configCommonParams);
return (
<>
<BaseLibraryPage
spaceId={spaceId}
ref={basePageRef}
entityConfigs={[
pluginConfig,
workflowConfig,
knowledgeConfig,
promptConfig,
databaseConfig,
]}
/>
{pluginModals}
{workflowModals}
{promptModals}
{databaseModals}
{knowledgeModals}
</>
);
};

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types='@coze-arch/bot-typings' />