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,5 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
});

View File

@@ -0,0 +1,16 @@
# @coze-studio/workspace-adapter
> Project template for react component with storybook.
## Features
- [x] eslint & ts
- [x] esm bundle
- [x] umd bundle
- [x] storybook
## Commands
- init: `rush update`
- dev: `npm run dev`
- build: `npm run build`

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["dist"]
}
]
}

View File

@@ -0,0 +1,7 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {},
});

View File

@@ -0,0 +1,64 @@
{
"name": "@coze-studio/workspace-adapter",
"version": "0.0.1",
"description": "工作空间菜单栏入口页面",
"license": "Apache-2.0",
"author": "duwenhan@bytedance.com",
"maintainers": [],
"exports": {
"./develop": "./src/pages/develop/index.tsx",
"./library": "./src/pages/library/index.tsx"
},
"main": "src/index.ts",
"typesVersions": {
"*": {
"develop": [
"./src/pages/develop/index.tsx"
],
"library": [
"./src/pages/library/index.tsx"
]
}
},
"scripts": {
"build": "exit 0",
"dev": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"@coze-arch/bot-api": "workspace:*",
"@coze-arch/bot-tea": "workspace:*",
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
"@coze-arch/i18n": "workspace:*",
"@coze-arch/idl": "workspace:*",
"@coze-foundation/space-store-adapter": "workspace:*",
"@coze-studio/workspace-base": "workspace:*",
"classnames": "^2.3.2"
},
"devDependencies": {
"@coze-arch/bot-typings": "workspace:*",
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/stylelint-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/lodash-es": "^4.17.10",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@vitest/coverage-v8": "~3.0.5",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"stylelint": "^15.11.0",
"vite-plugin-svgr": "~3.3.0",
"vitest": "~3.0.5"
},
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0"
}
}

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' />

View File

@@ -0,0 +1,51 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"jsx": "react-jsx",
"lib": ["DOM", "ESNext"],
"module": "ESNext",
"target": "ES2020",
"moduleResolution": "bundler",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
},
"include": ["src"],
"exclude": ["node_modules", "dist"],
"references": [
{
"path": "../../../arch/bot-api/tsconfig.build.json"
},
{
"path": "../../../arch/bot-tea/tsconfig.build.json"
},
{
"path": "../../../arch/bot-typings/tsconfig.build.json"
},
{
"path": "../../../arch/i18n/tsconfig.build.json"
},
{
"path": "../../../arch/idl/tsconfig.build.json"
},
{
"path": "../../../../config/eslint-config/tsconfig.build.json"
},
{
"path": "../../../../config/stylelint-config/tsconfig.build.json"
},
{
"path": "../../../../config/ts-config/tsconfig.build.json"
},
{
"path": "../../../../config/vitest-config/tsconfig.build.json"
},
{
"path": "../entry-base/tsconfig.build.json"
},
{
"path": "../../../foundation/space-store-adapter/tsconfig.build.json"
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"exclude": ["**/*"],
"compilerOptions": {
"composite": true
},
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.misc.json"
}
]
}

View File

@@ -0,0 +1,20 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"rootDir": "./",
"outDir": "./dist",
"jsx": "react-jsx",
"lib": ["DOM", "ESNext"],
"module": "ESNext",
"target": "ES2020",
"moduleResolution": "bundler"
},
"include": ["__tests__", "vitest.config.ts", "stories"],
"exclude": ["./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
]
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineConfig } from '@coze-arch/vitest-config';
export default defineConfig({
dirname: __dirname,
preset: 'web',
});

View File

@@ -0,0 +1,31 @@
import { mergeConfig } from 'vite';
import svgr from 'vite-plugin-svgr';
/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.tsx'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-onboarding',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
viteFinal: config =>
mergeConfig(config, {
plugins: [
svgr({
svgrOptions: {
native: false,
},
}),
],
}),
};
export default config;

View File

@@ -0,0 +1,14 @@
/** @type { import('@storybook/react').Preview } */
const preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;

View File

@@ -0,0 +1,5 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
});

View File

@@ -0,0 +1,16 @@
# @flow-studio/workspace
workspace 入口package
## Features
- [x] eslint & ts
- [x] esm bundle
- [x] umd bundle
- [x] storybook
## Commands
- init: `rush update`
- dev: `npm run dev`
- build: `npm run build`

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["./dist"]
}
]
}

View File

@@ -0,0 +1,7 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {},
});

View File

@@ -0,0 +1,126 @@
{
"name": "@coze-studio/workspace-base",
"version": "0.0.1",
"description": "Coze 2.0 workspace业务能力入口",
"license": "Apache-2.0",
"author": "sunzhiyuan.evan@bytedance.com",
"maintainers": [],
"exports": {
".": "./src/index.tsx",
"./knowledge-upload": "./src/pages/knowledge-upload/index.tsx",
"./knowledge-preview": "./src/pages/knowledge-preview/index.tsx",
"./develop": "./src/pages/develop/index.tsx",
"./library": "./src/pages/library/index.tsx"
},
"main": "src/index.tsx",
"typesVersions": {
"*": {
".": [
"./src/index.tsx"
],
"knowledge-upload": [
"./src/pages/knowledge-upload/index.tsx"
],
"knowledge-preview": [
"./src/pages/knowledge-preview/index.tsx"
],
"develop": [
"./src/pages/develop/index.tsx"
],
"library": [
"./src/pages/library/index.tsx"
]
}
},
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"@coze-agent-ide/bot-plugin": "workspace:*",
"@coze-agent-ide/space-bot": "workspace:*",
"@coze-arch/bot-api": "workspace:*",
"@coze-arch/bot-error": "workspace:*",
"@coze-arch/bot-flags": "workspace:*",
"@coze-arch/bot-semi": "workspace:*",
"@coze-arch/bot-space-api": "workspace:*",
"@coze-arch/bot-studio-store": "workspace:*",
"@coze-arch/bot-tea": "workspace:*",
"@coze-arch/bot-utils": "workspace:*",
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
"@coze-arch/i18n": "workspace:*",
"@coze-arch/idl": "workspace:*",
"@coze-arch/logger": "workspace:*",
"@coze-arch/report-events": "workspace:*",
"@coze-common/biz-components": "workspace:*",
"@coze-common/chat-area-utils": "workspace:*",
"@coze-common/coze-mitt": "workspace:*",
"@coze-common/prompt-kit": "workspace:*",
"@coze-common/prompt-kit-adapter": "workspace:*",
"@coze-community/components": "workspace:*",
"@coze-data/database-v2": "workspace:*",
"@coze-data/knowledge-ide-adapter": "workspace:*",
"@coze-data/knowledge-ide-base": "workspace:*",
"@coze-data/knowledge-modal-adapter": "workspace:*",
"@coze-data/knowledge-resource-processor-adapter": "workspace:*",
"@coze-data/knowledge-resource-processor-base": "workspace:*",
"@coze-data/knowledge-resource-processor-core": "workspace:*",
"@coze-data/knowledge-stores": "workspace:*",
"@coze-data/utils": "workspace:*",
"@coze-foundation/account-adapter": "workspace:*",
"@coze-foundation/enterprise-store-adapter": "workspace:*",
"@coze-foundation/layout": "workspace:*",
"@coze-foundation/local-storage": "workspace:*",
"@coze-studio/bot-plugin-store": "workspace:*",
"@coze-studio/bot-utils": "workspace:*",
"@coze-studio/components": "workspace:*",
"@coze-studio/premium-components-adapter": "workspace:*",
"@coze-studio/premium-store-adapter": "workspace:*",
"@coze-studio/project-entity-adapter": "workspace:*",
"@coze-studio/user-store": "workspace:*",
"@coze-workflow/base": "workspace:*",
"@coze-workflow/components": "workspace:*",
"ahooks": "^3.7.8",
"axios": "^1.4.0",
"classnames": "^2.3.2",
"immer": "^10.0.3",
"lodash-es": "^4.17.21",
"mitt": "^3.0.1",
"qs": "^6.11.2"
},
"devDependencies": {
"@coze-arch/bot-typings": "workspace:*",
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/stylelint-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@rsbuild/core": "1.1.13",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/lodash-es": "^4.17.10",
"@types/node": "18.18.9",
"@types/qs": "^6.9.7",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@vitest/coverage-v8": "~3.0.5",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"react-is": ">= 16.8.0",
"react-router-dom": "^6.22.0",
"styled-components": ">= 2",
"stylelint": "^15.11.0",
"typescript": "~5.8.2",
"vite": "^4.3.9",
"vite-plugin-svgr": "~3.3.0",
"vitest": "~3.0.5",
"webpack": "~5.91.0"
},
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0"
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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 } from 'react';
import { Avatar } from '@coze-arch/coze-design';
export interface CreatorProps {
avatar?: string;
name?: string;
extra?: string;
}
export const Creator: FC<CreatorProps> = ({ avatar, name, extra }) => (
<div className="flex items-center gap-x-[4px] h-[16px] coz-fg-secondary text-[12px] leading-16px">
{/* 社区版无多人协作功能,不展示资源所有者信息 */}
{IS_OPEN_SOURCE ? null : (
<>
<Avatar className="w-[16px] h-[16px] flex-shrink-0" src={avatar} />
<div className="text-nowrap">{name}</div>
<div className="w-3px h-3px rounded-full bg-[var(--coz-fg-secondary)]" />
</>
)}
<div className="text-ellipsis whitespace-nowrap overflow-hidden">
{extra}
</div>
</div>
);

View File

@@ -0,0 +1,140 @@
/*
* 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, { type HTMLAttributes, forwardRef } from 'react';
import classNames from 'classnames';
export type LayoutBaseProps = HTMLAttributes<HTMLDivElement>;
export const Layout = forwardRef<HTMLDivElement, LayoutBaseProps>(
({ children, ...restProps }, ref) => (
<div
{...restProps}
ref={ref}
className={classNames(
restProps.className,
'min-h-[100%]',
'flex flex-col gap-[16px]',
'overflow-hidden',
'px-[24px] pt-[24px]',
)}
>
{children}
</div>
),
);
export const Header = forwardRef<HTMLDivElement, LayoutBaseProps>(
({ children, ...restProps }, ref) => (
<div
{...restProps}
ref={ref}
className={classNames(
restProps.className,
'flex-shrink-0',
'w-full h-[32px]',
'flex items-center justify-between',
)}
>
{children}
</div>
),
);
export const HeaderTitle = forwardRef<HTMLDivElement, LayoutBaseProps>(
({ children, ...restProps }, ref) => (
<div
{...restProps}
ref={ref}
className={classNames(
restProps.className,
'text-[20px] font-[500]',
'flex items-center gap-[8px]',
)}
>
{children}
</div>
),
);
export const HeaderActions = forwardRef<HTMLDivElement, LayoutBaseProps>(
({ children, ...restProps }, ref) => (
<div
{...restProps}
ref={ref}
className={classNames(
restProps.className,
'flex items-center gap-[8px] ml-[32px]',
)}
>
{children}
</div>
),
);
export const SubHeader = forwardRef<HTMLDivElement, LayoutBaseProps>(
({ children, ...restProps }, ref) => (
<div
{...restProps}
ref={ref}
className={classNames(
restProps.className,
'flex-shrink-0',
'w-full h-[32px]',
'flex items-center justify-between',
)}
>
{children}
</div>
),
);
export const SubHeaderFilters = forwardRef<HTMLDivElement, LayoutBaseProps>(
({ children, ...restProps }, ref) => (
<div
{...restProps}
ref={ref}
className={classNames(restProps.className, 'flex items-center gap-[8px]')}
>
{children}
</div>
),
);
export const SubHeaderSearch = forwardRef<HTMLDivElement, LayoutBaseProps>(
({ children, ...restProps }, ref) => (
<div {...restProps} ref={ref} className={classNames(restProps.className)}>
{children}
</div>
),
);
export const Content = forwardRef<HTMLDivElement, LayoutBaseProps>(
({ children, ...restProps }, ref) => (
<div
{...restProps}
ref={ref}
className={classNames(
restProps.className,
'flex-grow',
'overflow-x-hidden overflow-y-auto',
)}
>
{children}
</div>
),
);

View File

@@ -0,0 +1,51 @@
/*
* 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 } from 'react';
import { I18n } from '@coze-arch/i18n';
import { IconCozEmpty, IconCozBroom } from '@coze-arch/coze-design/icons';
import { Button } from '@coze-arch/coze-design';
interface WorkspaceEmptyProps {
onClear?: () => void; // 清空按钮点击事件
hasFilter?: boolean; // 是否有筛选项
}
export const WorkspaceEmpty: FC<WorkspaceEmptyProps> = ({
onClear,
hasFilter = false,
}) => (
<div className="w-full h-full flex flex-col items-center pt-[120px]">
<IconCozEmpty className="w-[48px] h-[48px] coz-fg-dim" />
<div className="text-[16px] font-[500] leading-[22px] mt-[8px] mb-[16px] coz-fg-primary">
{I18n.t(
hasFilter ? 'library_empty_no_results_found_under' : 'search_not_found',
)}
</div>
{hasFilter ? (
<Button
color="primary"
icon={<IconCozBroom />}
onClick={() => {
onClear?.();
}}
>
{I18n.t('library_empty_clear_filters')}
</Button>
) : null}
</div>
);

View File

@@ -0,0 +1,19 @@
/*
* 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 highlightFilterStyle = {
border: '1px solid var(--semi-color-focus-border)',
};

View File

@@ -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.
*/
export { default as Plugin } from './pages/plugin';
export { default as Tool } from './pages/tool';
export { default as MocksetDetail } from './pages/mockset';
export { default as MocksetList } from './pages/mockset-list';
// !Notice 禁止直接导出 knowledge 相关代码,避免首屏加载
// export { default as KnowledgePreviewPage } from './pages/knowledge-preview';
// export { default as KnowledgeUploadPage } from './pages/knowledge-upload';
export { default as DatabaseDetailPage } from './pages/database';
export {
resourceNavigate as pluginResourceNavigate,
compareObjects,
} from './utils';
// 公共组件
export { Creator } from './components/creator';
export {
Content,
Header,
HeaderActions,
HeaderTitle,
Layout,
SubHeader,
SubHeaderFilters,
} from './components/layout/list';
export { WorkspaceEmpty } from './components/workspace-empty';
// constants
export { highlightFilterStyle } from './constants/filter-style';

View File

@@ -0,0 +1,60 @@
/*
* 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, useParams } from 'react-router-dom';
import React from 'react';
import qs from 'qs';
import {
KnowledgeParamsStoreProvider,
type IKnowledgeParams,
} from '@coze-data/knowledge-stores';
import { DatabaseDetail } from '@coze-data/database-v2';
const DatabaseDetailPage = () => {
const urlParams = useParams();
const queryParams = new URLSearchParams(location.search);
const navigate = useNavigate();
const params: IKnowledgeParams = {
botID: queryParams.get('bot_id') || '',
pageMode: (queryParams.get('page_mode') ||
'normal') as IKnowledgeParams['pageMode'],
biz: (queryParams.get('biz') || 'library') as IKnowledgeParams['biz'],
workflowID: queryParams.get('workflow_id') || '',
agentID: queryParams.get('agent_id') || '',
tableID: urlParams.table_id || '',
initialTab: (queryParams.get('initial_tab') ||
'structure') as IKnowledgeParams['initialTab'],
};
return (
<KnowledgeParamsStoreProvider
params={params}
resourceNavigate={{
// eslint-disable-next-line max-params
toResource: (resource, resourceID, query, opts) =>
navigate(
`/space/${params.spaceID}/${resource}/${resourceID}?${qs.stringify(query)}`,
opts,
),
}}
>
<DatabaseDetail />
</KnowledgeParamsStoreProvider>
);
};
export default DatabaseDetailPage;

View File

@@ -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 { useRequest } from 'ahooks';
import {
type IntelligenceBasicInfo,
IntelligenceStatus,
TaskAction,
} from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
import {
IconCozLoading,
IconCozWarningCircleFillPalette,
} from '@coze-arch/coze-design/icons';
import { Button, Space } from '@coze-arch/coze-design';
import { intelligenceApi } from '@coze-arch/bot-api';
export interface CopyProcessMaskProps {
intelligenceBasicInfo: IntelligenceBasicInfo;
onRetry?: (status: IntelligenceStatus | undefined) => void;
onCancelCopyAfterFailed?: (status: IntelligenceStatus | undefined) => void;
}
export const CopyProcessMask: React.FC<CopyProcessMaskProps> = ({
intelligenceBasicInfo,
onRetry,
onCancelCopyAfterFailed,
}) => {
const { status } = intelligenceBasicInfo;
const { run } = useRequest(
async (action: TaskAction) => {
const response = await intelligenceApi.ProcessEntityTask({
entity_id: intelligenceBasicInfo.id,
action,
});
return response.data?.entity_task?.entity_status;
},
{
manual: true,
onSuccess: (res, [action]) => {
if (action === TaskAction.ProjectCopyCancel) {
onCancelCopyAfterFailed?.(res);
}
if (action === TaskAction.ProjectCopyRetry) {
onRetry?.(res);
}
},
},
);
if (
status !== IntelligenceStatus.CopyFailed &&
status !== IntelligenceStatus.Copying
) {
return null;
}
return (
<div className="absolute w-full h-full flex items-center justify-center backdrop-blur-[6px] bg-[rgba(255,255,255,0.8)] left-0 top-0">
<div className="coz-fg-secondary flex flex-col items-center gap-y-[12px]">
{status === IntelligenceStatus.Copying ? (
<>
<IconCozLoading className="animate-spin" />
<div>{I18n.t('project_ide_duplicate_loading')}</div>
</>
) : null}
{status === IntelligenceStatus.CopyFailed ? (
<>
<IconCozWarningCircleFillPalette className="coz-fg-hglt-red" />
<div>{I18n.t('develop_list_card_copy_fail')}</div>
<Space spacing={8}>
<Button
color="primary"
onClick={() => {
run(TaskAction.ProjectCopyCancel);
}}
>
{I18n.t('Cancel')}
</Button>
<Button
color="hgltplus"
onClick={() => {
run(TaskAction.ProjectCopyRetry);
}}
>
{I18n.t('project_ide_toast_duplicate_fail_retry')}
</Button>
</Space>
</>
) : null}
</div>
</div>
);
};

View File

@@ -0,0 +1,51 @@
/*
* 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 } from 'react';
import { Typography } from '@coze-arch/coze-design';
export interface DescriptionProps {
description?: string;
}
const Description: FC<DescriptionProps> = ({ description }) => (
<Typography.Text
className="coz-fg-secondary text-[14px] leading-[20px] break-words"
ellipsis={{
showTooltip: {
opts: {
theme: 'dark',
content: (
<Typography.Text
className="break-words break-all coz-fg-white"
onClick={e => e.stopPropagation()}
ellipsis={{ showTooltip: false, rows: 16 }}
>
{description}
</Typography.Text>
),
},
type: 'tooltip',
},
rows: 2,
}}
>
{description}
</Typography.Text>
);
export default Description;

View File

@@ -0,0 +1,399 @@
/*
* 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 @coze-arch/max-line-per-function: ["error", {"max": 500}] */
/* eslint-disable complexity */
import { useNavigate } from 'react-router-dom';
import { useMemo, useState, type ReactNode } from 'react';
import { cloneDeep } from 'lodash-es';
import classNames from 'classnames';
import { FavoriteIconBtn } from '@coze-community/components';
import { ProductEntityType } from '@coze-arch/idl/product_api';
import {
type IntelligenceBasicInfo,
type IntelligenceData,
IntelligenceStatus,
IntelligenceType,
} from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
import {
IconCozCheckMarkCircleFillPalette,
IconCozMore,
IconCozStarFill,
IconCozWarningCircleFill,
} from '@coze-arch/coze-design/icons';
import { Avatar, IconButton, Menu, Tooltip } from '@coze-arch/coze-design';
import { formatDate, getFormatDateType } from '@coze-arch/bot-utils';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { ConnectorDynamicStatus } from '@coze-arch/bot-api/developer_api';
import { Creator } from '@/components/creator';
import Name from './name';
import { type AgentCopySuccessCallback, MenuCopyBot } from './menu-actions';
import { IntelligenceTag } from './intelligence-tag';
import Description from './description';
import { CopyProcessMask } from './copy-process-mask';
export interface BotCardProps {
intelligenceInfo: IntelligenceData;
timePrefixType?: 'recentOpen' | 'publish' | 'edit';
/**
* 返回 true 时会打断默认的跳转行为
*/
onClick?: (() => true) | (() => void);
onDelete?: (param: {
name: string;
id: string;
type: IntelligenceType;
}) => void;
onCopyProject?: (basicInfo: IntelligenceBasicInfo) => void;
onCopyAgent?: AgentCopySuccessCallback;
onUpdateIntelligenceInfo: (info: IntelligenceData) => void;
onRetryCopy: (basicInfo: IntelligenceBasicInfo) => void;
onCancelCopyAfterFailed: (basicInfo: IntelligenceBasicInfo) => void;
extraMenu?: ReactNode;
headerExtra?: ReactNode;
statusExtra?: ReactNode;
actionsMenuVisible?: boolean;
}
// eslint-disable-next-line max-lines-per-function
export const BotCard: React.FC<BotCardProps> = ({
intelligenceInfo,
timePrefixType,
onClick,
onDelete,
onCopyProject,
onCopyAgent,
onUpdateIntelligenceInfo,
onCancelCopyAfterFailed,
onRetryCopy,
extraMenu,
actionsMenuVisible = true,
headerExtra,
statusExtra,
}) => {
const navigate = useNavigate();
const {
basic_info,
type,
permission_info: { in_collaboration, can_delete } = {},
publish_info: { publish_time, connectors, has_published } = {},
other_info: { recently_open_time } = {},
owner_info,
favorite_info: { is_fav } = {},
} = intelligenceInfo;
const { id, name, icon_url, space_id, description, update_time, status } =
basic_info ?? {};
const hideOperation = useSpaceStore(store => store.space.hide_operation);
const renderPublishStatusIcon = () => {
if (!has_published) {
return null;
}
if (!connectors?.length) {
return (
<IconCozCheckMarkCircleFillPalette className="text-xxl coz-fg-hglt-green flex-shrink-0" />
);
}
const isSomeConnectorsFailed = connectors.some(
item => item?.connector_status !== ConnectorDynamicStatus.Normal,
);
if (isSomeConnectorsFailed) {
return (
<IconCozWarningCircleFill className="text-xxl coz-fg-hglt-yellow flex-shrink-0" />
);
}
return (
<IconCozCheckMarkCircleFillPalette className="text-xxl coz-fg-hglt-green flex-shrink-0" />
);
};
if (!id || !space_id) {
// id 和 space id 对 bot 卡片来说是必须的,这里约束一下 ts 类型
throw Error('No botID or no spaceID which are necessary');
}
const isBanned = status === IntelligenceStatus.Banned;
const isAgent = type === IntelligenceType.Bot;
const isProject = type === IntelligenceType.Project;
const timePrefix = useMemo(() => {
switch (timePrefixType) {
case 'recentOpen':
return I18n.t('develop_list_rank_tag_opened');
case 'publish':
return I18n.t('bot_list_rank_tag_published');
case 'edit':
return in_collaboration
? I18n.t('devops_publish_multibranch_RecentSubmit')
: I18n.t('bot_list_rank_tag_edited');
default:
}
}, [timePrefixType, in_collaboration]);
const time = useMemo(() => {
let timestamp: string | undefined;
switch (timePrefixType) {
case 'recentOpen':
timestamp = recently_open_time;
break;
case 'publish':
timestamp = publish_time;
break;
case 'edit':
timestamp = update_time;
break;
default:
}
return formatDate(Number(timestamp), getFormatDateType(Number(timestamp)));
}, [timePrefixType, publish_time, update_time, recently_open_time]);
// 是否展示 card 复层操作按钮
const [showActions, setShowActions] = useState(false);
// 是否展示 menu 菜单,这里有其他组件主动调用,需要受控
const [showMenu, setShowMenu] = useState(false);
return (
<>
<div
className={classNames([
'flex-grow h-[158px] min-w-[280px]',
'rounded-[6px] border-solid border-[1px] ',
'relative',
'overflow-hidden transition duration-150 ease-out hover:shadow-[0_6px_8px_0_rgba(28,31,35,6%)]',
'coz-stroke-primary coz-mg-card',
])}
>
<div
className="h-full w-full cursor-pointer flex flex-col gap-[12px] px-[16px] py-[16px]"
onClick={() => {
if (onClick?.()) {
return;
}
if (isBanned) {
return;
}
if (isAgent) {
navigate(`/space/${space_id}/bot/${id}`);
return;
}
if (isProject) {
navigate(`/space/${space_id}/project-ide/${id}`);
return;
}
}}
onMouseEnter={() => {
setShowActions(true);
}}
onMouseLeave={() => {
setShowActions(false);
}}
data-testid="bot-list-page.bot-card"
>
{/* 展示迁移失败状态 icon */}
{statusExtra}
{/* bot 基本信息 */}
<div className="flex justify-between">
<div className="flex flex-col gap-[4px] w-[calc(100%-76px)]">
<div className="flex items-center gap-[4px]">
<Name name={name} />
{isBanned ? (
// 如果失效了,高优展示失效 icon
<IconCozWarningCircleFill className="text-xxl coz-fg-hglt-red flex-shrink-0" />
) : (
<>
{/* 发布状态 icon */}
{renderPublishStatusIcon()}
{headerExtra}
</>
)}
</div>
<Description description={description} />
</div>
<Avatar
className="w-[64px] h-[64px] rounded-[10px] flex-shrink-0 ml-[12px]"
shape="square"
src={icon_url}
/>
</div>
{/* 项目/智能体 */}
<IntelligenceTag intelligenceType={type} />
{/* bot 作者信息 */}
{!!owner_info && (
<Creator
avatar={owner_info.avatar_url}
name={owner_info.nickname}
extra={`${timePrefix} ${time}`}
/>
)}
{/* actions 浮层 action 浮层出现的时候下方有一个白色遮罩 */}
{!hideOperation ? (
<>
{showActions && actionsMenuVisible ? (
<div
className="absolute bottom-[16px] right-[16px] w-[100px] h-[16px] "
style={{
background:
'linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 21.38%)',
}}
></div>
) : null}
<div
className="absolute bottom-[16px] right-[16px] flex gap-[4px]"
onClick={e => {
// 阻止 click 事件冒泡到 card 最外层
e.stopPropagation();
}}
>
{showActions && actionsMenuVisible ? (
<>
{!isBanned ? (
// 收藏 bot
<FavoriteIconBtn
useButton
isVisible
entityId={id}
entityType={
type === IntelligenceType.Bot
? ProductEntityType.Bot
: ProductEntityType.Project
}
isFavorite={is_fav}
onFavoriteStateChange={isFav => {
const clonedInfo = cloneDeep(intelligenceInfo);
clonedInfo.favorite_info = {
...(clonedInfo.favorite_info ?? {}),
is_fav: isFav,
};
onUpdateIntelligenceInfo(clonedInfo);
}}
/>
) : null}
{/* 下拉菜单 */}
<Menu
keepDOM
className="w-fit mt-4px mb-4px"
position="bottomRight"
trigger="custom"
visible={showMenu}
render={
<Menu.SubMenu mode="menu">
{/* 复制 bot */}
{isAgent ? (
<MenuCopyBot
id={id}
spaceID={space_id}
disabled={isBanned}
onCopySuccess={onCopyAgent}
onClose={() => setShowActions(false)}
/>
) : null}
{isProject ? (
<Tooltip content={I18n.t('coze_copy_to_tips_1')}>
<Menu.Item
onClick={() => {
if (!basic_info) {
return;
}
onCopyProject?.(basic_info);
}}
data-testid="bot-card.copy"
>
{I18n.t('project_ide_create_duplicate')}
</Menu.Item>
</Tooltip>
) : null}
{extraMenu}
{/* 删除 bot */}
<Tooltip
position="left"
trigger={can_delete ? 'custom' : 'hover'}
content={I18n.t(
'project_delete_permission_tooltips',
)}
>
<Menu.Item
type="danger"
disabled={!can_delete}
onClick={() => {
if (!name || !type) {
return;
}
onDelete?.({ name, id, type });
}}
>
<span>{I18n.t('Delete')}</span>
</Menu.Item>
</Tooltip>
</Menu.SubMenu>
}
>
<IconButton
className="rotate-90"
data-testid="bot-card.icon-more-button"
color="primary"
size="default"
icon={<IconCozMore />}
onClick={() => setShowMenu(true)}
/>
</Menu>
</>
) : is_fav && !isBanned ? (
// 如果 bot 已经收藏了,非 hover 时展示 icon
<IconButton
className="!pt-[20px]"
color="secondary"
icon={<IconCozStarFill className="coz-fg-color-yellow" />}
></IconButton>
) : null}
</div>
</>
) : null}
</div>
{basic_info ? (
<CopyProcessMask
intelligenceBasicInfo={basic_info}
onRetry={changedStatus => {
onRetryCopy({
...basic_info,
status: changedStatus,
});
}}
onCancelCopyAfterFailed={changedStatus => {
onCancelCopyAfterFailed({
...basic_info,
status: changedStatus,
});
}}
/>
) : null}
</div>
</>
);
};

View File

@@ -0,0 +1,51 @@
/*
* 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 { IntelligenceType } from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
import { Tag } from '@coze-arch/coze-design';
export interface IntelligenceTagProps {
intelligenceType: IntelligenceType | undefined;
}
export const IntelligenceTag: React.FC<IntelligenceTagProps> = ({
intelligenceType,
}) => {
if (intelligenceType === IntelligenceType.Project) {
return (
<Tag color="brand" size="small" className="w-fit">
{I18n.t('develop_list_card_tag_project')}
</Tag>
);
}
if (intelligenceType === IntelligenceType.Bot) {
return (
<Tag color="primary" size="small" className="w-fit">
{I18n.t('develop_list_card_tag_agent')}
</Tag>
);
}
// 社区版暂不支持该功能
if (intelligenceType === IntelligenceType.DouyinAvatarBot) {
return (
<Tag color="red" size="small" className="w-fit">
{/* TODO: i18n 文案 */}
</Tag>
);
}
return null;
};

View File

@@ -0,0 +1,232 @@
/*
* 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 { useRef, type FC } from 'react';
import { cozeMitt } from '@coze-common/coze-mitt';
import { logger } from '@coze-arch/logger';
import { type User } from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
import { IconCozWarningCircleFill } from '@coze-arch/coze-design/icons';
import { Menu, Toast, Tooltip } from '@coze-arch/coze-design';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import { useUIModal } from '@coze-arch/bot-semi';
import { CustomError } from '@coze-arch/bot-error';
import { DeveloperApi } from '@coze-arch/bot-api';
export interface MenuCommonProps {
id: string;
spaceID: string;
}
export interface MenuAnalysisProps extends MenuCommonProps {
disabled?: boolean;
}
export type AgentCopySuccessCallback = (param: {
templateId: string;
id: string;
name: string;
ownerInfo: Required<User>;
}) => void;
export const MenuAnalysis: FC<MenuAnalysisProps> = ({
disabled,
spaceID,
id,
}) => {
const navigate = useNavigate();
return (
<Menu.Item
disabled={disabled}
onClick={() => {
navigate(`/space/${spaceID}/bot/${id}/analysis`);
}}
>
{I18n.t('analytics_page_title')}
</Menu.Item>
);
};
export interface MenuCopyBotProps extends MenuCommonProps {
disabled?: boolean;
name?: string;
onCopySuccess?: AgentCopySuccessCallback;
onClose?: () => void;
}
export const MenuCopyBot: FC<MenuCopyBotProps> = ({
disabled,
id,
name,
spaceID,
onCopySuccess,
onClose,
}) => {
const lock = useRef(false);
const copyBot = async () => {
try {
lock.current = true;
const response = await DeveloperApi.DuplicateDraftBot({
space_id: spaceID,
bot_id: id,
});
Toast.success({
content: I18n.t('bot_duplicateded_toast'),
showClose: false,
});
const {
bot_id = '',
name: newBotName = '',
user_info = {},
} = response.data;
const {
id: userId = '',
name: userName = '',
avatar_url = '',
user_unique_name = '',
user_label = {},
} = user_info;
onCopySuccess?.({
templateId: id,
id: bot_id,
name: newBotName,
ownerInfo: {
user_id: userId,
nickname: userName,
avatar_url,
user_unique_name,
user_label,
},
});
} catch (error) {
logger.error({
error: new CustomError('copy bot', 'copy bot error'),
});
} finally {
onClose?.();
lock.current = false;
}
};
return (
<Tooltip
trigger={disabled ? 'custom' : 'hover'}
content={I18n.t('coze_copy_to_tips_1')}
>
<Menu.Item
data-testid="bot-card.copy"
disabled={disabled}
onClick={() => {
if (lock.current) {
return;
}
sendTeaEvent(EVENT_NAMES.bot_duplicate_click, {
bot_type: 'team_bot',
});
// team bot header
sendTeaEvent(EVENT_NAMES.bot_duplicate_click_front, {
bot_type: 'team_bot',
bot_id: id,
bot_name: name,
from: 'bots_card',
source: 'bots_card',
});
copyBot();
}}
>
{I18n.t('duplicate')}
</Menu.Item>
</Tooltip>
);
};
export interface MenuDeleteBotProps extends MenuCommonProps {
onDeleteSuccess?: () => void;
onClick?: () => void;
onClose?: () => void;
}
export const MenuDeleteBot: FC<MenuDeleteBotProps> = ({
spaceID,
id,
onDeleteSuccess,
onClick,
onClose,
}) => {
const deleteBot = async () => {
try {
await DeveloperApi.DeleteDraftBot({
space_id: spaceID,
bot_id: id,
});
Toast.success({
content: I18n.t('bot_deleted_toast'),
showClose: false,
});
onDeleteSuccess?.();
cozeMitt.emit('refreshFavList', {
id,
numDelta: -1,
});
} catch (error) {
logger.error({
error: new CustomError('delete bot', 'delete bot error'),
});
}
};
const { open, close, modal } = useUIModal({
type: 'info',
title: I18n.t('bot_delete_confirm_title'),
onOk: async () => await deleteBot(),
okText: I18n.t('Delete'),
cancelText: I18n.t('Cancel'),
icon: <IconCozWarningCircleFill className="text-24px coz-fg-hglt-red" />,
onCancel: () => {
close();
onClose?.();
},
okButtonProps: {
type: 'danger',
},
});
return (
<>
<Menu.Item
type="danger"
onClick={() => {
open();
onClick?.();
}}
>
<span className="coz-fg-hglt-red">{I18n.t('Delete')}</span>
</Menu.Item>
{modal(
<>
{I18n.t('bot_list_delete_bot', {
platform: FLOW_BRAND_NAME,
})}
</>,
)}
</>
);
};

View File

@@ -0,0 +1,44 @@
/*
* 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 } from 'react';
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
import { IconCozWorkflow } from '@coze-arch/coze-design/icons';
import { Typography } from '@coze-arch/coze-design';
export interface ModelInfoProps {
showWorkflowMode?: boolean;
name?: string;
}
const ModelInfo: FC<ModelInfoProps> = ({ showWorkflowMode, name }) => (
<Typography.Text
className="text-[12px] leading-[16px] coz-fg-dim"
ellipsis={{ showTooltip: { opts: { theme: 'dark' } }, rows: 1 }}
>
{showWorkflowMode ? (
<div className="flex items-center">
<IconCozWorkflow className="mr-[2px]" />
{I18n.t('Workflow Mode' as I18nKeysNoOptionsType)}
</div>
) : (
name
)}
</Typography.Text>
);
export default ModelInfo;

View File

@@ -0,0 +1,44 @@
/*
* 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 } from 'react';
import { Typography } from '@coze-arch/coze-design';
export interface NameProps {
name?: string;
}
const Name: FC<NameProps> = ({ name }) => (
<Typography.Text
className="text-[16px] font-[500] leading-[22px]"
ellipsis={{
showTooltip: {
opts: {
content: <span onClick={e => e.stopPropagation()}>{name}</span>,
style: { wordBreak: 'break-word' },
theme: 'dark',
},
type: 'tooltip',
},
rows: 1,
}}
>
{name}
</Typography.Text>
);
export default Name;

View File

@@ -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.
*/
import { SearchScope } from '@coze-arch/idl/intelligence_api';
import {
DevelopCustomPublishStatus,
DevelopCustomTypeStatus,
type FilterParamsType,
} from './type';
export const CREATOR_FILTER_OPTIONS = [
{
value: SearchScope.All,
labelI18NKey: 'bot_list_team',
},
{
value: SearchScope.CreateByMe,
labelI18NKey: 'bot_list_mine',
},
] as const;
export const STATUS_FILTER_OPTIONS = [
{
value: DevelopCustomPublishStatus.All,
labelI18NKey: 'filter_all',
},
{
value: DevelopCustomPublishStatus.Publish,
labelI18NKey: 'Published_1',
},
{
value: 'recentOpened',
labelI18NKey: 'filter_develop_recent_opened',
},
] as const;
export const TYPE_FILTER_OPTIONS = [
{
value: DevelopCustomTypeStatus.All,
labelI18NKey: 'filter_develop_all_types',
},
{
value: DevelopCustomTypeStatus.Project,
labelI18NKey: 'filter_develop_project',
},
{
value: DevelopCustomTypeStatus.Agent,
labelI18NKey: 'filter_develop_agent',
},
] as const;
export const FILTER_PARAMS_DEFAULT: FilterParamsType = {
searchScope: SearchScope.All,
searchValue: '',
isPublish: DevelopCustomPublishStatus.All,
searchType: DevelopCustomTypeStatus.All,
recentlyOpen: undefined,
};

View File

@@ -0,0 +1,91 @@
/*
* 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 { isObject, merge } from 'lodash-es';
import { useDebounceFn, useUpdateEffect } from 'ahooks';
import { safeJSONParse } from '@coze-agent-ide/space-bot/util';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import { localStorageService } from '@coze-foundation/local-storage';
import { type FilterParamsType } from '../type';
import { FILTER_PARAMS_DEFAULT } from '../develop-filter-options';
const isPersistentFilterParamsType = (
params: unknown,
): params is Partial<FilterParamsType> => isObject(params);
const getDefaultFilterParams = async () => {
const localFilterParams = await localStorageService.getValueSync(
'workspace-develop-filters',
);
if (!localFilterParams) {
return FILTER_PARAMS_DEFAULT;
}
const parsedFilterParams = safeJSONParse(localFilterParams) as unknown;
if (isPersistentFilterParamsType(parsedFilterParams)) {
return merge({}, FILTER_PARAMS_DEFAULT, parsedFilterParams);
}
return FILTER_PARAMS_DEFAULT;
};
export const useCachedQueryParams = () => {
const [filterParams, setFilterParams] = useState<FilterParamsType>(
FILTER_PARAMS_DEFAULT,
);
useUpdateEffect(() => {
/** 当筛选条件变化时,取合适的 key 存入本地 */
const { searchScope, isPublish, recentlyOpen, searchType } = filterParams;
localStorageService.setValue(
'workspace-develop-filters',
JSON.stringify({
searchScope,
isPublish,
searchType,
recentlyOpen,
}),
);
}, [filterParams]);
useEffect(() => {
/** 异步读取本地存储的筛选条件 */
getDefaultFilterParams().then(filters => {
setFilterParams(prev => merge({}, prev, filters));
});
}, []);
const debouncedSetSearchValue = useDebounceFn(
(searchValue = '') => {
setFilterParams(params => ({
...params,
searchValue,
}));
// tea 埋点
sendTeaEvent(EVENT_NAMES.search_front, {
full_url: location.href,
source: 'develop',
search_word: searchValue,
});
},
{
wait: 300,
},
);
return [filterParams, setFilterParams, debouncedSetSearchValue.run] as const;
};

View File

@@ -0,0 +1,156 @@
/*
* 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 Dispatch, type SetStateAction } from 'react';
import { cloneDeep, merge } from 'lodash-es';
import { produce } from 'immer';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import {
type IntelligenceBasicInfo,
type IntelligenceData,
} from '@coze-arch/bot-api/intelligence_api';
import { useCopyProjectModal } from '@coze-studio/project-entity-adapter';
import { type DraftIntelligenceList } from '../type';
import { produceCopyIntelligenceData } from '../page-utils/copy';
import { type AgentCopySuccessCallback } from '../components/bot-card/menu-actions';
export const useCardActions = ({
isPersonalSpace,
mutate,
}: {
isPersonalSpace: boolean;
mutate: Dispatch<SetStateAction<DraftIntelligenceList | undefined>>;
}) => {
const { modalContextHolder: copyModalHolder, openModal: onCopyProject } =
useCopyProjectModal({
onSuccess: ({ basicInfo, templateId, ownerInfo }) => {
mutate(prev =>
produce(prev, draft => {
const target = draft?.list.find(
intelligence => intelligence.basic_info?.id === templateId,
);
if (!target) {
return;
}
const copyData = produceCopyIntelligenceData({
originTemplateData: target,
newCopyData: { basicInfo, ownerInfo },
});
draft?.list.unshift(copyData);
}),
);
},
});
const mutateIntelligenceBasicInfo = (info: IntelligenceBasicInfo) => {
mutate(prev =>
produce(prev, draft => {
const target = draft?.list.find(i => i.basic_info?.id === info.id);
if (!target) {
return;
}
target.basic_info = info;
}),
);
};
const onCopyAgent: AgentCopySuccessCallback = param => {
mutate(prev =>
produce(prev, draft => {
const target = draft?.list.find(
intelligence => intelligence.basic_info?.id === param.templateId,
);
if (!target) {
return;
}
const copyData = produceCopyIntelligenceData({
originTemplateData: target,
newCopyData: {
ownerInfo: param.ownerInfo,
basicInfo: merge({}, target.basic_info, {
id: param.id,
name: param.name,
}),
},
});
draft?.list.unshift(copyData);
}),
);
};
const onDeleteMutate = ({ id }: { id: string }) => {
mutate(prev =>
produce(prev, draft => {
if (!draft?.list) {
return;
}
draft.list = draft.list.filter(item => item.basic_info?.id !== id);
}),
);
};
const onUpdate = (intelligenceData: IntelligenceData) => {
mutate(prev => {
if (!prev) {
return undefined;
}
const idx = prev.list.findIndex(
item => item.basic_info?.id === intelligenceData.basic_info?.id,
);
if (idx < 0) {
return;
}
const clonedList = cloneDeep(prev?.list ?? []);
clonedList.splice(idx, 1, intelligenceData);
return {
...prev,
list: clonedList,
};
});
};
const onClick = (intelligenceData: IntelligenceData) => {
sendTeaEvent(EVENT_NAMES.workspace_action_front, {
space_id: intelligenceData.basic_info?.space_id ?? '',
space_type: isPersonalSpace ? 'personal' : 'teamspace',
tab_name: 'develop',
action: 'click',
id: intelligenceData.basic_info?.id,
name: intelligenceData.basic_info?.name,
type: 'agent',
});
};
return {
contextHolder: <>{copyModalHolder}</>,
actions: {
onClick,
onCopyProject,
onCopyAgent,
onUpdate,
onRetryCopy: mutateIntelligenceBasicInfo,
onCancelCopyAfterFailed: mutateIntelligenceBasicInfo,
onDeleteMutate,
},
};
};

View File

@@ -0,0 +1,62 @@
/*
* 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 } from 'react';
import {
cozeMitt,
type RefreshFavListParams,
type CreateProjectByCopyTemplateFromSidebarParam,
} from '@coze-common/coze-mitt';
export const useGlobalEventListeners = ({
reload,
spaceId,
}: {
reload: () => void;
spaceId: string;
}) => {
useEffect(() => {
const handlerRefreshFavList = (
refreshFavListParams: RefreshFavListParams,
) => {
// 只在工作空间收藏取消收藏变化的时候刷新列表
if (refreshFavListParams.emitPosition === 'favorites-list-item') {
reload();
}
};
const handleReloadConditionally = (
eventParam: CreateProjectByCopyTemplateFromSidebarParam,
) => {
if (eventParam.toSpaceId !== spaceId) {
return;
}
reload();
};
cozeMitt.on('refreshFavList', handlerRefreshFavList);
cozeMitt.on(
'createProjectByCopyTemplateFromSidebar',
handleReloadConditionally,
);
return () => {
cozeMitt.off('refreshFavList', handlerRefreshFavList);
cozeMitt.off(
'createProjectByCopyTemplateFromSidebar',
handleReloadConditionally,
);
};
}, []);
};

View File

@@ -0,0 +1,123 @@
/*
* 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 { type SetStateAction, type Dispatch, type ReactNode } from 'react';
import { I18n } from '@coze-arch/i18n';
import {
type DeleteIntelligenceParam,
useCreateProjectModal,
useDeleteIntelligence,
type CreateProjectHookProps,
} from '@coze-studio/project-entity-adapter';
import { cozeMitt } from '@coze-common/coze-mitt';
import { Toast } from '@coze-arch/coze-design';
import { type DraftIntelligenceList } from '../type';
const showCreateSuccessToast = () => {
Toast.success({
content: I18n.t('creat_project_toast_success'),
showClose: false,
});
};
export const useIntelligenceActions = ({
spaceId,
mutateList,
reloadList,
extraGuideButtonConfigs,
}: {
spaceId: string;
reloadList: () => void;
mutateList: Dispatch<SetStateAction<DraftIntelligenceList | undefined>>;
extraGuideButtonConfigs?: CreateProjectHookProps['extraGuideButtonConfigs'];
}): {
contextHolder: ReactNode;
actions: {
createIntelligence: () => void;
deleteIntelligence: (param: DeleteIntelligenceParam) => void;
};
} => {
const navigate = useNavigate();
const navigateToProjectIDE = (inputProjectId: string) =>
navigate(`/space/${spaceId}/project-ide/${inputProjectId}`);
const {
modalContextHolder: createModalContextHolder,
createProject: createIntelligence,
} = useCreateProjectModal({
selectSpace: false,
bizCreateFrom: 'space',
initialSpaceId: spaceId,
extraGuideButtonConfigs,
onCreateBotSuccess: botId => {
if (botId) {
navigate(`/space/${spaceId}/bot/${botId}`);
}
},
onCreateProjectSuccess: ({ projectId }) => {
showCreateSuccessToast();
navigateToProjectIDE(projectId);
},
onCopyProjectTemplateSuccess: () => {
reloadList();
},
});
const handleDeleteIntelligenceAndMutate = (mutateDeleteId: string) => {
Toast.success({
content: I18n.t('project_ide_toast_delete_success'),
showClose: false,
});
cozeMitt.emit('refreshFavList', { id: mutateDeleteId, numDelta: -1 });
mutateList(prev =>
prev
? {
...prev,
list: prev.list.filter(
item => item.basic_info?.id !== mutateDeleteId,
),
}
: undefined,
);
};
const { modalContextHolder: deleteModalContextHolder, deleteIntelligence } =
useDeleteIntelligence({
onDeleteAgentSuccess: agentParam => {
handleDeleteIntelligenceAndMutate(agentParam.agentId);
},
onDeleteProjectSuccess: projectParam => {
handleDeleteIntelligenceAndMutate(projectParam.projectId);
},
});
return {
contextHolder: (
<>
{createModalContextHolder}
{deleteModalContextHolder}
</>
),
actions: {
createIntelligence,
deleteIntelligence,
},
};
};

View File

@@ -0,0 +1,203 @@
/*
* 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, useRef } from 'react';
import axios, { type CancelTokenSource } from 'axios';
import { type InfiniteScrollOptions } from 'ahooks/lib/useInfiniteScroll/types';
import { useInfiniteScroll } from 'ahooks';
import { withSlardarIdButton } from '@coze-studio/bot-utils';
import {
createReportEvent,
REPORT_EVENTS as ReportEventNames,
} from '@coze-arch/report-events';
import { logger } from '@coze-arch/logger';
import {
IntelligenceStatus,
type IntelligenceType,
type search,
type SearchScope,
} from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
import { Toast } from '@coze-arch/coze-design';
import { intelligenceApi } from '@coze-arch/bot-api';
import { type DraftIntelligenceList } from '../type';
const pageSize = 24;
export interface FilterParamsType {
types: IntelligenceType[];
spaceId: string;
hasPublished?: boolean;
searchValue?: string;
recentlyOpen?: boolean;
searchScope?: SearchScope;
orderBy: search.OrderBy;
}
const getIntelligenceList = async (
dataSource: DraftIntelligenceList | undefined,
{
spaceId,
types,
searchValue,
hasPublished,
recentlyOpen,
searchScope,
orderBy,
}: FilterParamsType,
cancelTokenRef: React.MutableRefObject<CancelTokenSource | null>,
) => {
// 每次新的请求,都重置一下 cancel token
const source = axios.CancelToken.source();
cancelTokenRef.current = source;
const resp = await intelligenceApi
.GetDraftIntelligenceList(
{
space_id: spaceId,
name: searchValue,
types,
size: pageSize,
has_published: hasPublished,
recently_open: recentlyOpen,
cursor_id: dataSource?.nextCursorId,
search_scope: searchScope,
// 固定值,来自历史代码
order_by: orderBy,
status: [
IntelligenceStatus.Using,
IntelligenceStatus.Banned,
IntelligenceStatus.MoveFailed,
],
},
{ cancelToken: source.token, __disableErrorToast: true },
)
.catch(e => {
if (e.message !== 'canceled') {
Toast.error({
content: withSlardarIdButton(e.msg || e.message || I18n.t('error')),
showClose: false,
});
}
});
if (resp?.data) {
return {
list: resp.data.intelligences ?? [],
hasMore: Boolean(resp.data.has_more),
nextCursorId: resp.data.next_cursor_id,
};
} else {
return {
list: [],
hasMore: false,
nextCursorId: undefined,
};
}
};
const buildBotLogger = logger.createLoggerWith({
ctx: {
namespace: 'bot_list',
},
});
const getBotListReportEvent = createReportEvent({
eventName: ReportEventNames.getBotList,
logger: buildBotLogger,
});
export const useIntelligenceList = ({
params: {
spaceId,
types,
searchValue,
hasPublished,
recentlyOpen,
searchScope,
orderBy,
},
onBefore,
onSuccess,
onError,
}: {
params: FilterParamsType;
} & Pick<
InfiniteScrollOptions<DraftIntelligenceList>,
'onBefore' | 'onSuccess' | 'onError'
>) => {
const containerRef = useRef<HTMLDivElement>(null);
const cancelTokenRef = useRef<CancelTokenSource | null>(null);
const listResp = useInfiniteScroll<DraftIntelligenceList>(
async dataSource =>
await getIntelligenceList(
dataSource,
{
spaceId,
types,
searchValue,
hasPublished,
recentlyOpen,
searchScope,
orderBy,
},
cancelTokenRef,
),
{
target: containerRef,
reloadDeps: [
types.join(','),
searchValue,
hasPublished,
recentlyOpen,
searchScope,
orderBy,
spaceId,
],
isNoMore: dataSource => !dataSource?.hasMore,
onBefore: () => {
if (listResp.loadingMore || listResp.loading) {
cancelTokenRef.current?.cancel();
}
getBotListReportEvent.start();
onBefore?.();
},
onSuccess: (...res) => {
getBotListReportEvent.success();
onSuccess?.(...res);
},
onError: e => {
getBotListReportEvent.error({
error: e,
reason: e.message,
});
onError?.(e);
},
},
);
useEffect(
() => () => {
// 取消正在请求的接口
cancelTokenRef.current?.cancel();
},
[spaceId],
);
return { listResp, containerRef, cancelTokenRef };
};

View File

@@ -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 { useNavigate } from 'react-router-dom';
import { type Dispatch, type SetStateAction, useEffect } from 'react';
import { produce } from 'immer';
import {
type IntelligenceData,
IntelligenceStatus,
IntelligenceType,
TaskAction,
} from '@coze-arch/idl/intelligence_api';
import { CopyTaskType } from '@coze-arch/idl';
import { I18n } from '@coze-arch/i18n';
import { Button, Toast } from '@coze-arch/coze-design';
import { intelligenceApi } from '@coze-arch/bot-api';
import { type DraftIntelligenceList } from '../type';
import {
intelligenceCopyTaskPollingService,
type PollCopyTaskEvent,
} from '../service/intelligence-copy-task-polling-service';
const registerCopyTaskPolling = (data: IntelligenceData[]) => {
intelligenceCopyTaskPollingService.registerPolling(
data
.filter(
intelligence =>
intelligence.type === IntelligenceType.Project &&
intelligence.basic_info?.status === IntelligenceStatus.Copying,
)
.map(i => ({
entity_id: i.basic_info?.id,
task_type: CopyTaskType.ProjectCopy,
})),
);
};
export const useProjectCopyPolling = ({
spaceId,
listData,
mutate,
}: {
spaceId: string;
mutate: Dispatch<SetStateAction<DraftIntelligenceList | undefined>>;
listData?: IntelligenceData[];
}) => {
const navigate = useNavigate();
const navigateToProjectIDE = (inputProjectId: string) =>
navigate(`/space/${spaceId}/project-ide/${inputProjectId}`);
useEffect(() => {
if (listData) {
registerCopyTaskPolling(listData);
}
}, [listData]);
useEffect(() => {
const onTaskUpdate = (list: PollCopyTaskEvent['onCopyTaskUpdate']) => {
mutate(prev =>
produce(prev, draft => {
list.forEach(task => {
const target = draft?.list.find(
intelligence => intelligence.basic_info?.id === task.entity_id,
);
if (!target || !target.basic_info) {
return;
}
target.basic_info.status = task.entity_status;
});
}),
);
// 需要重新封装下
list.forEach(item => {
if (item.entity_status === IntelligenceStatus.Using) {
const successToastId = Toast.success({
content: (
<>
{I18n.t('project_ide_toast_duplicate_success')}
<Button
className="ml-6px"
color="primary"
onClick={() => {
Toast.close(successToastId);
navigateToProjectIDE(item.entity_id ?? '');
}}
>
{I18n.t('project_ide_toast_duplicate_view')}
</Button>
</>
),
showClose: false,
});
return;
}
if (item.entity_status === IntelligenceStatus.CopyFailed) {
const failedToastId = Toast.error({
content: (
<>
{I18n.t('project_ide_toast_duplicate_fail')}
<Button
className="ml-6px"
color="primary"
onClick={async () => {
Toast.close(failedToastId);
const response = await intelligenceApi.ProcessEntityTask({
entity_id: item.entity_id,
action: TaskAction.ProjectCopyRetry,
});
mutate(prev =>
produce(prev, draft => {
const target = draft?.list.find(
intelligence =>
intelligence.basic_info?.id === item.entity_id,
);
if (!target || !target.basic_info) {
return;
}
target.basic_info.status =
response.data?.entity_task?.entity_status;
}),
);
}}
>
{I18n.t('project_ide_toast_duplicate_fail_retry')}
</Button>
</>
),
showClose: false,
});
return;
}
});
};
intelligenceCopyTaskPollingService.eventCenter.on(
'onCopyTaskUpdate',
onTaskUpdate,
);
return () => {
intelligenceCopyTaskPollingService.clearAll();
intelligenceCopyTaskPollingService.eventCenter.off(
'onCopyTaskUpdate',
onTaskUpdate,
);
};
}, []);
};

View File

@@ -0,0 +1,61 @@
/*
* 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 {
Content,
Header,
HeaderActions,
HeaderTitle,
Layout,
SubHeader,
SubHeaderFilters,
SubHeaderSearch,
} from '@/components/layout/list';
export { highlightFilterStyle } from '../../constants/filter-style';
export { WorkspaceEmpty } from '../../components/workspace-empty';
export { DevelopCustomPublishStatus, DevelopCustomTypeStatus } from './type';
export {
isPublishStatus,
isSearchScopeEnum,
isRecentOpen,
} from './page-utils/predicate';
export {
getPublishRequestParam,
getTypeRequestParams,
} from './page-utils/parameters';
export {
isEqualDefaultFilterParams,
isFilterHighlight,
} from './page-utils/filters';
export {
CREATOR_FILTER_OPTIONS,
FILTER_PARAMS_DEFAULT,
STATUS_FILTER_OPTIONS,
TYPE_FILTER_OPTIONS,
} from './develop-filter-options';
export { useCardActions } from './hooks/use-card-actions';
export { useIntelligenceList } from './hooks/use-intelligence-list';
export { useIntelligenceActions } from './hooks/use-intelligence-actions';
export { useGlobalEventListeners } from './hooks/use-global-event-listeners';
export { useProjectCopyPolling } from './hooks/use-project-copy-polling';
export { useCachedQueryParams } from './hooks/use-cached-query-params';
export { BotCard } from './components/bot-card';
export interface DevelopProps {
spaceId: string;
}

View File

@@ -0,0 +1,58 @@
/*
* 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 { produce } from 'immer';
import {
type IntelligenceBasicInfo,
type IntelligenceData,
type User,
} from '@coze-arch/idl/intelligence_api';
import { getUserInfo, getUserLabel } from '@coze-foundation/account-adapter';
export const produceCopyIntelligenceData = ({
originTemplateData,
newCopyData,
}: {
originTemplateData: IntelligenceData;
newCopyData: {
ownerInfo: User | undefined;
basicInfo: IntelligenceBasicInfo;
};
}) => {
// 这是 fallback
const userInfo = getUserInfo();
const userLabel = getUserLabel();
return produce<IntelligenceData>(originTemplateData, draft => {
const { type } = draft;
const { ownerInfo, basicInfo } = newCopyData;
return {
type,
owner_info: ownerInfo || {
user_id: userInfo?.user_id_str,
nickname: userInfo?.name,
avatar_url: userInfo?.avatar_url,
user_unique_name: userInfo?.app_user_info.user_unique_name,
user_label: userLabel || undefined,
},
basic_info: basicInfo,
permission_info: {
in_collaboration: false,
can_delete: true,
can_view: true,
},
};
});
};

View File

@@ -0,0 +1,62 @@
/*
* 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 { exhaustiveCheckForRecord } from '@coze-common/chat-area-utils';
import { SearchScope } from '@coze-arch/idl/intelligence_api';
import { DevelopCustomTypeStatus, type FilterParamsType } from '../type';
import { FILTER_PARAMS_DEFAULT } from '../develop-filter-options';
export const isEqualDefaultFilterParams = ({
filterParams,
}: {
filterParams: FilterParamsType;
}) => {
const {
searchScope,
searchValue,
searchType,
isPublish,
recentlyOpen,
...rest
} = filterParams;
exhaustiveCheckForRecord(rest);
return (
searchScope === FILTER_PARAMS_DEFAULT.searchScope &&
searchType === FILTER_PARAMS_DEFAULT.searchType &&
isPublish === FILTER_PARAMS_DEFAULT.isPublish &&
recentlyOpen === FILTER_PARAMS_DEFAULT.recentlyOpen &&
!searchValue
);
};
export const isFilterHighlight = (currentFilterParams: FilterParamsType) => {
const {
searchValue,
searchScope,
isPublish,
searchType,
recentlyOpen,
...rest
} = currentFilterParams;
exhaustiveCheckForRecord(rest);
return {
isIntelligenceTypeFilterHighlight:
searchType !== DevelopCustomTypeStatus.All,
isOwnerFilterHighlight: searchScope !== SearchScope.All,
isPublishAndOpenFilterHighlight: isPublish || recentlyOpen,
};
};

View File

@@ -0,0 +1,58 @@
/*
* 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 { IntelligenceType } from '@coze-arch/idl/intelligence_api';
import { DevelopCustomPublishStatus, DevelopCustomTypeStatus } from '../type';
export const getPublishRequestParam = (
publishStatus: DevelopCustomPublishStatus | undefined,
) => {
if (typeof publishStatus === 'undefined') {
return;
}
if (publishStatus === DevelopCustomPublishStatus.All) {
return;
}
return publishStatus === DevelopCustomPublishStatus.Publish;
};
/**
* 项目类型请求前后端参数映射将DevelopCustomTypeStatus映射为IntelligenceType[]
* 需要根据是否可以展示抖音分身来决定是否处理 DouyinAvatarBot
* @param type
* @returns
*/
export const getTypeRequestParams = ({
type,
}: {
type: DevelopCustomTypeStatus;
}) => {
const allIntelligenceTypeParams = [
IntelligenceType.Bot,
IntelligenceType.Project,
];
const typeMap: Record<DevelopCustomTypeStatus, IntelligenceType[]> = {
[DevelopCustomTypeStatus.All]: allIntelligenceTypeParams,
[DevelopCustomTypeStatus.Agent]: [IntelligenceType.Bot],
[DevelopCustomTypeStatus.Project]: [IntelligenceType.Project],
[DevelopCustomTypeStatus.DouyinAvatarBot]: [
IntelligenceType.DouyinAvatarBot,
],
};
return typeMap[type] || [];
};

View File

@@ -0,0 +1,36 @@
/*
* 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 { SearchScope } from '@coze-arch/idl/intelligence_api';
import { DevelopCustomPublishStatus } from '../type';
export function isPublishStatus(
val: unknown,
): val is DevelopCustomPublishStatus {
const statusList: unknown[] = [
DevelopCustomPublishStatus.All,
DevelopCustomPublishStatus.NoPublish,
DevelopCustomPublishStatus.Publish,
];
return statusList.includes(val);
}
export const isRecentOpen = (val: unknown) => val === 'recentOpened';
export const isSearchScopeEnum = (val: unknown): val is SearchScope =>
val === SearchScope.All || val === SearchScope.CreateByMe;

View File

@@ -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 mitt, { type Emitter } from 'mitt';
import { uniqBy } from 'lodash-es';
import {
type EntityTaskData,
IntelligenceStatus,
type TaskStruct,
} from '@coze-arch/idl/intelligence_api';
import { intelligenceApi } from '@coze-arch/bot-api';
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type PollCopyTaskEvent = {
onCopyTaskUpdate: EntityTaskData[];
};
export class IntelligenceCopyTaskPollingService {
readonly defaultTimeout = 2000;
readonly timeoutStep = 2000;
taskPool: TaskStruct[] = [];
timeout = this.defaultTimeout;
timerId: ReturnType<typeof setTimeout> | null = null;
eventCenter: Emitter<PollCopyTaskEvent>;
constructor() {
this.eventCenter = mitt<PollCopyTaskEvent>();
}
removeTaskPoll = (params: EntityTaskData[]) => {
this.taskPool = this.taskPool.filter(
task => !params.find(p => p.entity_id === task.entity_id),
);
};
poll = async () => {
const response = await intelligenceApi.EntityTaskSearch({
task_list: this.taskPool,
});
const taskMap = response.data?.entity_task_map ?? {};
const taskList = Object.entries(taskMap).map(([_, task]) => task);
const finishPollList = taskList.filter(
task => task.entity_status !== IntelligenceStatus.Copying,
);
this.removeTaskPoll(finishPollList);
this.eventCenter.emit('onCopyTaskUpdate', taskList);
};
resetTimeout = () => {
this.timeout = this.defaultTimeout;
};
increaseTimeout = () => {
this.timeout += this.timeoutStep;
};
checkIsContinuePoll = () => Boolean(this.taskPool.length);
clearTimer = () => {
if (!this.timerId) {
return;
}
clearTimeout(this.timerId);
};
run = () => {
this.timerId = setTimeout(async () => {
await this.poll();
if (!this.checkIsContinuePoll()) {
return;
}
this.increaseTimeout();
this.run();
}, this.timeout);
};
registerPolling = (params: TaskStruct[]) => {
const prevLength = this.taskPool.length;
this.taskPool = uniqBy(
this.taskPool.concat(params),
task => task.entity_id,
);
const currentLength = this.taskPool.length;
if (!prevLength && currentLength) {
this.resetTimeout();
this.run();
}
};
clearAll = () => {
this.clearTimer();
this.eventCenter.off('onCopyTaskUpdate');
this.taskPool = [];
this.timerId = null;
};
}
export const intelligenceCopyTaskPollingService =
new IntelligenceCopyTaskPollingService();

View File

@@ -0,0 +1,47 @@
/*
* 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 IntelligenceData,
type SearchScope,
} from '@coze-arch/idl/intelligence_api';
export enum DevelopCustomPublishStatus {
All = 0,
Publish = 1,
NoPublish = 2,
}
export enum DevelopCustomTypeStatus {
All = 0,
Project = 1,
Agent = 2,
DouyinAvatarBot = 3, // single agent 类型的抖音分身 社区版暂不支持该功能
}
export interface DraftIntelligenceList {
list: IntelligenceData[];
hasMore: boolean;
nextCursorId: string | undefined;
}
export interface FilterParamsType {
searchScope: SearchScope | undefined;
searchValue: string;
isPublish: DevelopCustomPublishStatus;
searchType: DevelopCustomTypeStatus;
recentlyOpen: boolean | undefined;
}

View File

@@ -0,0 +1,91 @@
/*
* 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, useParams } from 'react-router-dom';
import qs from 'qs';
import { KnowledgeParamsStoreProvider } from '@coze-data/knowledge-stores';
import {
type UnitType,
type OptType,
} from '@coze-data/knowledge-resource-processor-core';
import { type ActionType } from '@coze-data/knowledge-ide-base/types';
import {
BizAgentKnowledgeIDE,
BizLibraryKnowledgeIDE,
BizProjectKnowledgeIDE,
BizWorkflowKnowledgeIDE,
} from '@coze-data/knowledge-ide-adapter';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
export const KnowledgePreviewPage = () => {
const { dataset_id, space_id } = useParams();
const searchParams = new URLSearchParams(window.location.search);
const params = {
datasetID: dataset_id ?? '',
spaceID: space_id ?? '',
type: searchParams.get('type') as UnitType,
opt: searchParams.get('opt') as OptType,
docID: searchParams.get('doc_id') ?? '',
pageMode: searchParams.get('page_mode') as 'modal' | 'normal',
biz: searchParams.get('biz') as
| 'agentIDE'
| 'workflow'
| 'project'
| 'library',
botID: searchParams.get('bot_id') ?? '',
workflowID: searchParams.get('workflow_id') ?? '',
agentID: searchParams.get('agent_id') ?? '',
actionType: searchParams.get('action_type') as ActionType,
first_auto_open_edit_document_id:
searchParams.get('first_auto_open_edit_document_id') ?? '',
create: searchParams.get('create') ?? '',
};
const navigate = useNavigate();
const spaceID = useSpaceStore(store => store.space.id);
return (
<KnowledgeParamsStoreProvider
params={{ ...params, spaceID }}
resourceNavigate={{
// eslint-disable-next-line max-params
toResource: (resource, resourceID, query, opts) =>
navigate(
`/space/${params.spaceID}/${resource}/${resourceID}?${qs.stringify(query)}`,
opts,
),
upload: (query, opts) =>
navigate(
`/space/${params.spaceID}/knowledge/${params.datasetID}/upload?${qs.stringify(query)}`,
opts,
),
}}
>
{(() => {
if (params.biz === 'agentIDE') {
return <BizAgentKnowledgeIDE />;
}
if (params.biz === 'workflow') {
return <BizWorkflowKnowledgeIDE />;
}
if (params.biz === 'project') {
return <BizProjectKnowledgeIDE />;
}
// 默认'library'
return <BizLibraryKnowledgeIDE />;
})()}
</KnowledgeParamsStoreProvider>
);
};

View File

@@ -0,0 +1,84 @@
/*
* 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, useParams } from 'react-router-dom';
import qs from 'qs';
import {
KnowledgeParamsStoreProvider,
type IKnowledgeParams,
} from '@coze-data/knowledge-stores';
import {
OptType,
UnitType,
} from '@coze-data/knowledge-resource-processor-core';
import {
getUploadConfig,
KnowledgeResourceProcessor,
} from '@coze-data/knowledge-resource-processor-adapter';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
export const KnowledgeUploadPage = () => {
const navigate = useNavigate();
const spaceID = useSpaceStore(store => store.space.id);
const locationSearchParams = new URLSearchParams(location.search);
const type = (locationSearchParams.get('type') ||
UnitType.TEXT_DOC) as UnitType;
const opt = (locationSearchParams.get('opt') || OptType.ADD) as OptType;
const docID = locationSearchParams.get('doc_id') || '';
// 社区版暂不支持该功能
const isDouyinBot =
locationSearchParams.get('is_douyin') === 'true' ? true : false;
const { dataset_id, space_id } = useParams();
const params: IKnowledgeParams = {
datasetID: dataset_id || '',
spaceID: space_id || '',
type,
opt,
docID,
isDouyinBot,
biz: 'library',
};
const uploadConfig = getUploadConfig(
type ?? UnitType.TEXT,
opt ?? OptType.ADD,
);
if (!uploadConfig) {
return <></>;
}
return (
<KnowledgeParamsStoreProvider
params={{ ...params, spaceID }}
resourceNavigate={{
// eslint-disable-next-line max-params
toResource: (resource, resourceID, query, opts) =>
navigate(
`/space/${params.spaceID}/${resource}/${resourceID}?${qs.stringify(query)}`,
opts,
),
upload: (query, opts) =>
navigate(
`/space/${params.spaceID}/knowledge/${params.datasetID}/upload?${qs.stringify(query)}`,
opts,
),
}}
>
<KnowledgeResourceProcessor uploadConfig={uploadConfig} />
</KnowledgeParamsStoreProvider>
);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,86 @@
/*
* 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 ReactNode } from 'react';
import { IconCozCheckMarkCircleFill } from '@coze-arch/coze-design/icons';
import { Space, Typography, CozAvatar } from '@coze-arch/coze-design';
import {
PublishStatus,
type ResourceInfo,
} from '@coze-arch/bot-api/plugin_develop';
export const BaseLibraryItem: React.FC<{
resourceInfo: ResourceInfo;
defaultIcon?: string;
customAvatar?: ReactNode;
tag?: ReactNode;
}> = ({ resourceInfo, defaultIcon, customAvatar, tag }) => (
<div className="flex items-center w-full h-[48px]">
{customAvatar ?? (
<CozAvatar
size="lg"
className="overflow-hidden flex-shrink-0 mr-[12px] rounded-[12px]"
data-testid="workspace.library.item.avatar"
src={resourceInfo.icon || defaultIcon}
type="bot"
/>
)}
<div
className="flex flex-col gap-[2px]"
style={{ width: 'calc(100% - 60px)' }}
>
<div className="w-[95%] h-[20px] flex-shrink-0">
<Space spacing={4} className="w-full">
<Typography.Text
data-testid="workspace.library.item.name"
className="h-[20px] text-[14px] font-[500] coz-fg-primary leading-[20px]"
style={{
maxWidth: '246px',
}}
ellipsis={{ showTooltip: true }}
>
{resourceInfo.name}
</Typography.Text>
{resourceInfo.publish_status === PublishStatus.Published ? (
<IconCozCheckMarkCircleFill
data-testid="workspace.library.item.publish.status"
className="flex-shrink-0 w-[16px] h-[16px] coz-fg-hglt-green"
/>
) : null}
</Space>
</div>
{tag || resourceInfo.desc ? (
<div className="w-[95%] flex-shrink leading-[0]">
<Space spacing={4} className="w-full">
{tag}
{resourceInfo.desc ? (
<Typography.Text
data-testid="workspace.library.item.desc"
fontSize="12px"
className="!h-[16px] !font-[400] !coz-fg-secondary !leading-[16px]"
ellipsis={{ showTooltip: true }}
>
{resourceInfo.desc}
</Typography.Text>
) : null}
</Space>
</div>
) : null}
</div>
</div>
);

View File

@@ -0,0 +1,51 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { IconCozPlus } from '@coze-arch/coze-design/icons';
import { Button, Menu } from '@coze-arch/coze-design';
import { type LibraryEntityConfig } from '../types';
export const LibraryHeader: React.FC<{
entityConfigs: LibraryEntityConfig[];
}> = ({ entityConfigs }) => (
<div className="flex items-center justify-between mb-[16px]">
<div className="font-[500] text-[20px]">
{I18n.t('navigation_workspace_library')}
</div>
<Menu
position="bottomRight"
className="w-120px mt-4px mb-4px"
render={
<Menu.SubMenu mode="menu">
{entityConfigs.map(config => config.renderCreateMenu?.() ?? null)}
</Menu.SubMenu>
}
>
<Button
theme="solid"
type="primary"
icon={<IconCozPlus />}
data-testid="workspace.library.header.create"
>
{I18n.t('library_resource')}
</Button>
</Menu>
</div>
);

View File

@@ -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 { I18n } from '@coze-arch/i18n';
import {
PublishStatus,
ResType,
type LibraryResourceListRequest,
} from '@coze-arch/bot-api/plugin_develop';
export const LIBRARY_PAGE_SIZE = 15;
export type QueryParams = Omit<LibraryResourceListRequest, 'space_id' | 'size'>;
export const initialParam: QueryParams = {
cursor: '',
user_filter: 0,
publish_status_filter: 0,
res_type_filter: [-1],
name: '',
};
/** 是否由当前用户创建:
* 0-不筛选
* 1-当前用户 */
export const getScopeOptions = () => [
{
label: I18n.t('library_filter_tags_all_creators'),
value: 0,
},
{
label: I18n.t('library_filter_tags_created_by_me'),
value: 1,
},
];
/** 发布状态:
* 0-不筛选
* 1-未发布
* 2-已发布 */
export const getStatusOptions = () => [
{
label: I18n.t('library_filter_tags_all_status'),
value: 0,
},
{
label: I18n.t('library_filter_tags_published'),
value: PublishStatus.Published,
},
{
label: I18n.t('library_filter_tags_unpublished'),
value: PublishStatus.UnPublished,
},
];
/** event type */
export const eventLibraryType = {
[ResType.Plugin]: 'plugin',
[ResType.Workflow]: 'workflow',
[ResType.Imageflow]: 'imageflow',
[ResType.Knowledge]: 'knowledge',
[ResType.UI]: 'ui',
[ResType.Prompt]: 'prompt',
[ResType.Database]: 'database',
[ResType.Variable]: 'variable',
[ResType.Voice]: 'voice',
} as const;

View File

@@ -0,0 +1,120 @@
/*
* 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 { parse } from 'qs';
import { useUpdateEffect } from 'ahooks';
import { localStorageService } from '@coze-foundation/local-storage';
import { ResType } from '@coze-arch/idl/plugin_develop';
import { safeJSONParse } from '@coze-agent-ide/space-bot/util';
import { compareObjects } from '@/utils';
import { initialParam, type QueryParams } from '../consts';
/**
* 从url query中获取搜索参数 优先级最高高于LS缓存
* @returns {} | undefined
*/
const getSearchParamsFromUrl = () => {
const urlQuery = parse(location.search.slice(1)) as {
type?: string;
name?: string;
};
const searchParams: QueryParams = {};
if (urlQuery.type && Object.values(ResType).includes(Number(urlQuery.type))) {
const resType = Number(urlQuery.type);
searchParams.res_type_filter =
resType === ResType.Knowledge ? [resType, -1] : [resType];
}
if (urlQuery.name) {
searchParams.name = urlQuery.name;
}
return searchParams;
};
// 异步初始化获取筛选参数, 分别从LS缓存获取和url query获取
const getDefaultFilterParams = async () => {
const searchParamsFromUrl = getSearchParamsFromUrl();
const localFilterParams = await localStorageService.getValueSync(
'workspace-library-filters',
);
let defaultFilterParams = initialParam;
if (localFilterParams) {
const safeParams = safeJSONParse(localFilterParams) as QueryParams;
defaultFilterParams = { ...defaultFilterParams, ...safeParams };
}
// 图像流和工作流合并会删除资源中的图像流选项 这里转换成全部
if (defaultFilterParams?.res_type_filter?.[0] === 3) {
defaultFilterParams.res_type_filter[0] = -1;
}
defaultFilterParams = { ...defaultFilterParams, ...searchParamsFromUrl };
return defaultFilterParams;
};
export const useCachedQueryParams = ({ spaceId }: { spaceId: string }) => {
const [ready, setReady] = useState(false);
const [params, setParams] = useState<QueryParams>(initialParam);
const hasFilter = !compareObjects(params, initialParam, [
'res_type_filter',
'user_filter',
'publish_status_filter',
'name',
]);
/** 每次切换空间的时候,重新初始化筛选条件,并清空搜索框,重新请求资源列表 */
useEffect(() => {
setReady(false);
getDefaultFilterParams().then(filters => {
setParams(p => ({
...p,
...filters,
cursor: '', // 筛选、刷新时重置为空
}));
setReady(true);
});
}, [spaceId]);
useUpdateEffect(() => {
/** 当筛选条件变化时,取合适的 key 存入本地 */
const tempParams = {
res_type_filter: params.res_type_filter,
user_filter: params.user_filter,
publish_status_filter: params.publish_status_filter,
};
localStorageService.setValue(
'workspace-library-filters',
JSON.stringify(tempParams),
);
}, [params]);
const resetParams = () => {
setParams(initialParam);
};
return {
params,
setParams,
resetParams,
hasFilter,
ready,
} as const;
};

View File

@@ -0,0 +1,197 @@
/*
* 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 MouseEvent } from 'react';
import { useSize } from 'ahooks';
import { I18n } from '@coze-arch/i18n';
import {
type ColumnProps,
Space,
Avatar,
Typography,
} from '@coze-arch/coze-design';
import { responsiveTableColumn, formatDate } from '@coze-arch/bot-utils';
import {
type ResourceInfo,
type ResType,
} from '@coze-arch/bot-api/plugin_develop';
import { type LibraryEntityConfig } from '../types';
import { BaseLibraryItem } from '../components/base-library-item';
const { Text } = Typography;
// 预设表格cell最小宽度
const NAME_COL_WIDTH = 260;
const ACTIONS_COL_WIDTH = 60;
const TYPE_COL_WIDTH = 100;
const CREATOR_COL_WIDTH = 231;
const EDITED_TIME_COL_WIDTH = 150;
const stopPro = (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation(); //阻止冒泡
};
const getResTypeLabelFromConfigMap = (
item: ResourceInfo,
entityConfigs: LibraryEntityConfig[],
): string => {
if (item.res_type === undefined) {
return '-';
}
const target = entityConfigs.find(config =>
config.target.includes(item.res_type as ResType),
)?.typeFilter;
return target?.filterName ?? target?.label ?? '-';
};
export const useGetColumns = ({
entityConfigs,
reloadList,
isPersonalSpace,
}: {
entityConfigs: LibraryEntityConfig[];
reloadList: () => void;
isPersonalSpace: boolean;
}): ColumnProps<ResourceInfo>[] => {
const size = useSize(document.body);
const clientWidth = size?.width ?? document.body.clientWidth;
return [
{
title: I18n.t('library_name', {}, 'Resource'),
dataIndex: 'name',
width: responsiveTableColumn(clientWidth, NAME_COL_WIDTH),
render: (_text, record) => {
const config =
record.res_type !== undefined
? entityConfigs.find(c =>
c.target.includes(record.res_type as ResType),
)
: undefined;
if (config?.renderItem) {
return config.renderItem(record);
}
return <BaseLibraryItem resourceInfo={record} />;
},
},
{
title: I18n.t('library_type', {}, 'Type'),
dataIndex: 'res_type',
width: responsiveTableColumn(TYPE_COL_WIDTH, TYPE_COL_WIDTH),
render: (_v, record) => (
<div
data-testid="workspace.library.item.type"
className="text-[14px] font-[400]"
>
{getResTypeLabelFromConfigMap(record, entityConfigs)}
</div>
),
},
...(isPersonalSpace
? []
: ([
{
title: I18n.t('Plugin_list_table_owner'),
dataIndex: 'creator',
width: responsiveTableColumn(CREATOR_COL_WIDTH, CREATOR_COL_WIDTH),
render: (_v, record) => {
if (!record.creator_name) {
return '-';
}
return (
<Space style={{ width: '100%' }} spacing={6}>
<Avatar
data-testid="workspace.library.item.creator.avatar"
size="extra-small"
src={record.creator_avatar}
/>
<Text
data-testid="workspace.library.item.creator.name"
style={{
fontSize: 14,
fontWeight: 500,
color: 'var(--coz-fg-secondary)',
}}
ellipsis={{ showTooltip: true }}
>
{record.creator_name}
</Text>
<Text
data-testid="workspace.library.item.creator.username"
style={{
flex: 1,
fontSize: 14,
fontWeight: 400,
color: 'var(--coz-fg-secondary)',
}}
ellipsis={{ showTooltip: true }}
>
{`@${record.user_name}`}
</Text>
</Space>
);
},
},
] satisfies ColumnProps<ResourceInfo>[])),
{
title: I18n.t('library_edited_time', {}, 'Edited time'),
dataIndex: 'edit_time',
width: responsiveTableColumn(
EDITED_TIME_COL_WIDTH,
EDITED_TIME_COL_WIDTH,
),
render: (_v, record) => {
if (!record.edit_time) {
return '-';
}
return (
<div
data-testid="workspace.library.item.edit.time"
className="text-[14px] font-[400]"
>
{formatDate(Number(record.edit_time), 'YYYY-MM-DD HH:mm')}
</div>
);
},
},
{
title: I18n.t('library_actions', {}, 'Actions'),
dataIndex: 'action',
width: responsiveTableColumn(ACTIONS_COL_WIDTH, ACTIONS_COL_WIDTH),
render: (_v, record) => {
const config =
record.res_type !== undefined
? entityConfigs.find(c =>
c.target.includes(record.res_type as ResType),
)
: undefined;
return (
<div
data-testid="workspace.library.item.actions"
onClick={e => {
stopPro(e);
}}
>
{config?.renderActions(record, reloadList) ?? null}
</div>
);
},
},
];
};

View File

@@ -0,0 +1,31 @@
/*
* 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 ReactNode } from 'react';
import { type ResourceInfo } from '@coze-arch/idl/plugin_develop';
import { type TableActionProps } from '@coze-arch/coze-design';
import { type LibraryEntityConfig } from '../../types';
export type UseEntityConfigHook = (params: {
spaceId: string;
isPersonalSpace?: boolean;
reloadList: () => void;
getCommonActions?: (
item: ResourceInfo,
) => NonNullable<TableActionProps['actionList']>;
}) => { config: LibraryEntityConfig; modals: ReactNode };

View File

@@ -0,0 +1,117 @@
/*
* 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 { useRequest } from 'ahooks';
import {
ActionKey,
type ResourceInfo,
ResType,
} from '@coze-arch/idl/plugin_develop';
import { I18n } from '@coze-arch/i18n';
import { IconCozDatabase } from '@coze-arch/coze-design/icons';
import { Menu, Table, Toast } from '@coze-arch/coze-design';
import { MemoryApi } from '@coze-arch/bot-api';
import { useLibraryCreateDatabaseModal } from '@coze-data/database-v2';
import { type UseEntityConfigHook } from './types';
const { TableAction } = Table;
export const useDatabaseConfig: UseEntityConfigHook = ({
spaceId,
reloadList,
getCommonActions,
}) => {
const navigate = useNavigate();
const {
modal: createDatabaseModal,
open: openCreateDatabaseModal,
close: closeCreateDatabaseModal,
} = useLibraryCreateDatabaseModal({
enterFrom: 'library',
onFinish: databaseID => {
navigate(
`/space/${spaceId}/database/${databaseID}?page_modal=normal&biz=create`,
);
closeCreateDatabaseModal();
},
});
// delete action
const { run: deleteDatabase } = useRequest(
(databaseId: string) =>
MemoryApi.DeleteDatabase({
id: databaseId,
}),
{
manual: true,
onSuccess: () => {
reloadList();
Toast.success(I18n.t('Delete_success'));
},
},
);
return {
modals: <>{createDatabaseModal}</>,
config: {
typeFilter: {
label: I18n.t('new_db_001'),
value: ResType.Database,
},
renderCreateMenu: () => (
<Menu.Item
data-testid="workspace.library.header.create.card"
icon={<IconCozDatabase />}
onClick={openCreateDatabaseModal}
>
{I18n.t('new_db_001')}
</Menu.Item>
),
target: [ResType.Database],
onItemClick: (item: ResourceInfo) => {
navigate(
`/space/${spaceId}/database/${item.res_id}?page_mode=normal&from=library`,
);
},
renderActions: (item: ResourceInfo) => {
// 是否能删除
const deleteDisabled = !item.actions?.find(
action => action.key === ActionKey.Delete,
)?.enable;
// 是否启用
// delete operation
const deleteProps = {
disabled: deleteDisabled,
deleteDesc: I18n.t('library_delete_desc'),
handler: () => {
deleteDatabase(item.res_id || '');
},
};
return (
<TableAction
deleteProps={deleteProps}
actionList={getCommonActions?.(item)}
/>
);
},
},
};
};

View File

@@ -0,0 +1,272 @@
/*
* 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 { useRequest } from 'ahooks';
import { useCreateKnowledgeModalV2 } from '@coze-data/knowledge-modal-adapter';
import {
ActionKey,
type ResourceInfo,
ResType,
} from '@coze-arch/idl/plugin_develop';
import { DatasetStatus } from '@coze-arch/idl/knowledge';
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
import { IconCozClock, IconCozKnowledge } from '@coze-arch/coze-design/icons';
import { Menu, Switch, Tag, Toast, Table } from '@coze-arch/coze-design';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { safeJSONParse } from '@coze-agent-ide/space-bot/util';
import { BaseLibraryItem } from '../../components/base-library-item';
import DocDefaultIcon from '../../assets/doc_default_icon.png';
import { type UseEntityConfigHook } from './types';
const { TableAction } = Table;
/**
* 知识库tag
* 0-text
* 1-table
* 2-image
* */
const knowledgeSubTypeTextMap: Record<number, I18nKeysNoOptionsType> = {
0: 'library_filter_tags_text',
1: 'library_filter_tags_table',
2: 'library_filter_tags_image',
};
/**
* 禁用状态tag
* 3-disabled
* */
const knowledgeBizStatusTextMap: Record<number, I18nKeysNoOptionsType> = {
3: 'library_filter_tags_disabled',
};
enum KnowledgeBizStatus {
Disabled = 3,
}
const renderKnowledgeItem = (item: ResourceInfo) => {
const knowledgeTag =
item.res_sub_type !== undefined &&
knowledgeSubTypeTextMap[item.res_sub_type];
const knowledgeBizStatusTag =
item.biz_res_status !== undefined &&
knowledgeBizStatusTextMap[item.biz_res_status];
return (
<BaseLibraryItem
resourceInfo={item}
defaultIcon={DocDefaultIcon}
tag={
<>
{safeJSONParse(item.biz_extend?.processing_file_id_list)?.length ? (
<Tag
data-testid="workspace.library.item.tag"
color="brand"
size="mini"
className="flex-shrink-0 flex-grow-0"
prefixIcon={<IconCozClock />}
>
{I18n.t('library_filter_tags_processing')}
</Tag>
) : null}
{knowledgeTag ? (
<Tag
data-testid="workspace.library.item.tag"
color="brand"
size="mini"
className="flex-shrink-0 flex-grow-0"
>
{I18n.t(knowledgeTag)}
</Tag>
) : null}
{knowledgeBizStatusTag ? (
<Tag
data-testid="workspace.library.item.tag"
color="red"
size="mini"
className="flex-shrink-0 flex-grow-0"
>
{I18n.t(knowledgeBizStatusTag)}
</Tag>
) : null}
</>
}
/>
);
};
const getTypeFilters = () => ({
label: (
<span data-testid="space.library.filter.knowledge">
{I18n.t('library_resource_type_knowledge')}
</span>
),
filterName: I18n.t('library_resource_type_knowledge'),
value: ResType.Knowledge,
children: [
{
label: (
<span data-testid="space.library.filter.knowledge.all_types">
{I18n.t('library_filter_tags_all_types')}
</span>
),
value: -1,
},
{
label: (
<span data-testid="space.library.filter.knowledge.text">
{I18n.t('library_filter_tags_text')}
</span>
),
value: 0,
},
{
label: (
<span data-testid="space.library.filter.knowledge.table">
{I18n.t('library_filter_tags_table')}
</span>
),
value: 1,
},
{
label: (
<span data-testid="space.library.filter.knowledge.image">
{I18n.t('library_filter_tags_image')}
</span>
),
value: 2,
},
],
});
export const useKnowledgeConfig: UseEntityConfigHook = ({
spaceId,
reloadList,
getCommonActions,
}) => {
const navigate = useNavigate();
const {
modal: createKnowledgeModal,
open: openCreateKnowledgeModal,
close: closeCreateKnowledgeModal,
} = useCreateKnowledgeModalV2({
onFinish: (datasetID, unitType, shouldUpload) => {
navigate(
`/space/${spaceId}/knowledge/${datasetID}${shouldUpload ? '/upload' : ''}?type=${unitType}&from=create`,
);
closeCreateKnowledgeModal();
},
});
// 删除
const { run: delKnowledge } = useRequest(
(datasetId: string) =>
KnowledgeApi.DeleteDataset({
dataset_id: datasetId,
}),
{
manual: true,
onSuccess: () => {
reloadList();
Toast.success(I18n.t('Delete_success'));
},
},
);
// 开启开关
const { run: enableKnowledge, loading } = useRequest(
(enableStatus: boolean, record: ResourceInfo) =>
KnowledgeApi.UpdateDataset({
dataset_id: record.res_id,
name: record.name,
description: record.desc,
icon_uri: record.biz_extend?.icon_uri, // 从业务字段获取
status: enableStatus
? DatasetStatus.DatasetReady
: DatasetStatus.DatasetForbid,
}),
{
manual: true,
debounceWait: 300,
onSuccess: reloadList,
},
);
return {
modals: <>{createKnowledgeModal}</>,
config: {
typeFilter: getTypeFilters(),
renderCreateMenu: () => (
<Menu.Item
data-testid="workspace.library.header.create.knowledge"
icon={<IconCozKnowledge />}
onClick={openCreateKnowledgeModal}
>
{I18n.t('library_resource_type_knowledge')}
</Menu.Item>
),
target: [ResType.Knowledge],
onItemClick: (item: ResourceInfo) => {
navigate(`/space/${spaceId}/knowledge/${item.res_id}?from=library`);
},
renderItem: renderKnowledgeItem,
renderActions: (item: ResourceInfo) => {
const deleteDisabled = !item.actions?.find(
action => action.key === ActionKey.Delete,
)?.enable;
// knowledge 资源启用状态开关 enable 的是否禁用状态即switch标签的禁用状态
const enableDisabled = !item.actions?.find(
action => action.key === ActionKey.EnableSwitch,
)?.enable;
const deleteProps = {
disabled: deleteDisabled,
deleteDesc: I18n.t('library_delete_desc'),
handler: () => {
delKnowledge(item.res_id || '');
},
};
const enableProps = {
actionKey: 'enable',
actionText: I18n.t('library_actions_enable'),
disabled: enableDisabled || loading,
extActionDom: (
<Switch
size="mini"
disabled={enableDisabled}
loading={loading}
defaultChecked={Boolean(
item.biz_res_status !== KnowledgeBizStatus.Disabled,
)}
onChange={v => {
enableKnowledge(v, item);
}}
/>
),
};
return (
<TableAction
deleteProps={deleteProps}
actionList={[enableProps, ...(getCommonActions?.(item) ?? [])]}
/>
);
},
},
};
};

View File

@@ -0,0 +1,142 @@
/*
* 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 { useState } from 'react';
import {
ActionKey,
PluginType,
ResType,
type ResourceInfo,
} from '@coze-arch/idl/plugin_develop';
import { I18n } from '@coze-arch/i18n';
import { PluginDevelopApi } from '@coze-arch/bot-api';
import { useBotCodeEditOutPlugin } from '@coze-agent-ide/bot-plugin/hook';
import { CreateFormPluginModal } from '@coze-agent-ide/bot-plugin/component';
import { IconCozPlugin } from '@coze-arch/coze-design/icons';
import { Menu, Tag, Toast, Table } from '@coze-arch/coze-design';
import { BaseLibraryItem } from '../../components/base-library-item';
import PluginDefaultIcon from '../../assets/plugin_default_icon.png';
import { type UseEntityConfigHook } from './types';
const { TableAction } = Table;
export const usePluginConfig: UseEntityConfigHook = ({
spaceId,
reloadList,
getCommonActions,
}) => {
const [showFormPluginModel, setShowFormPluginModel] = useState(false);
const navigate = useNavigate();
const { modal: editPluginCodeModal, open } = useBotCodeEditOutPlugin({
modalProps: {
onSuccess: reloadList,
},
});
return {
modals: (
<>
<CreateFormPluginModal
isCreate={true}
visible={showFormPluginModel}
onSuccess={pluginID => {
navigate(`/space/${spaceId}/plugin/${pluginID}`);
reloadList();
}}
onCancel={() => {
setShowFormPluginModel(false);
}}
/>
{editPluginCodeModal}
</>
),
config: {
typeFilter: {
label: I18n.t('library_resource_type_plugin'),
value: ResType.Plugin,
},
renderCreateMenu: () => (
<Menu.Item
data-testid="workspace.library.header.create.plugin"
icon={<IconCozPlugin />}
onClick={() => {
setShowFormPluginModel(true);
}}
>
{I18n.t('library_resource_type_plugin')}
</Menu.Item>
),
target: [ResType.Plugin],
onItemClick: (item: ResourceInfo) => {
if (
item.res_type === ResType.Plugin &&
item.res_sub_type === 2 //Plugin1-Http; 2-App; 6-Local
) {
const disable = !item.actions?.find(
action => action.key === ActionKey.Delete,
)?.enable;
open(item.res_id || '', disable);
} else {
navigate(`/space/${spaceId}/plugin/${item.res_id}`);
}
},
renderItem: item => (
<BaseLibraryItem
resourceInfo={item}
defaultIcon={PluginDefaultIcon}
tag={
item.res_type === ResType.Plugin &&
item.res_sub_type === PluginType.LOCAL ? (
<Tag
data-testid="workspace.library.item.tag"
color="cyan"
size="mini"
className="flex-shrink-0 flex-grow-0"
>
{I18n.t('local_plugin_label')}
</Tag>
) : null
}
/>
),
renderActions: (item: ResourceInfo) => {
const deleteDisabled = !item.actions?.find(
action => action.key === ActionKey.Delete,
)?.enable;
const deleteProps = {
disabled: deleteDisabled,
deleteDesc: I18n.t('library_delete_desc'),
handler: async () => {
await PluginDevelopApi.DelPlugin({ plugin_id: item.res_id });
reloadList();
Toast.success(I18n.t('Delete_success'));
},
};
return (
<TableAction
deleteProps={deleteProps}
actionList={getCommonActions?.(item)}
/>
);
},
},
};
};

View File

@@ -0,0 +1,174 @@
/*
* 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 { useRef } from 'react';
import { useRequest } from 'ahooks';
import {
ActionKey,
ResType,
type ResourceInfo,
} from '@coze-arch/idl/plugin_develop';
import { type IntelligenceData } from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
import { IconCozLightbulb } from '@coze-arch/coze-design/icons';
import { Table, Menu, Toast } from '@coze-arch/coze-design';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import { useFlags } from '@coze-arch/bot-flags';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { useModal as useSelectIntelligenceModal } from '@coze-common/biz-components/select-intelligence-modal';
import { usePromptConfiguratorModal } from '@coze-common/prompt-kit-adapter/create-prompt';
import { type UseEntityConfigHook } from './types';
const { TableAction } = Table;
export const usePromptConfig: UseEntityConfigHook = ({
spaceId,
isPersonalSpace = true,
reloadList,
getCommonActions,
}) => {
const navigate = useNavigate();
const [FLAGS] = useFlags();
const recordRef = useRef<ResourceInfo | null>(null);
const { open: openSelectIntelligenceModal, node: selectIntelligenceModal } =
useSelectIntelligenceModal({
spaceId,
onSelect: (intelligence: IntelligenceData) => {
const targetId = intelligence.basic_info?.id;
const diffPromptResourceId = recordRef.current?.res_id;
navigate(`/space/${spaceId}/bot/${targetId}`, {
replace: true,
state: {
mode: 'diff',
diffPromptResourceId,
targetId,
},
});
sendTeaEvent(EVENT_NAMES.compare_mode_front, {
bot_id: targetId,
compare_type: 'prompts',
from: 'prompt_resource',
source: 'bot_detail_page',
action: 'start',
});
},
});
const { open: openCreatePrompt, node: promptConfiguratorModal } =
usePromptConfiguratorModal({
spaceId,
source: 'resource_library',
// 社区版暂不支持该功能
enableDiff: FLAGS['bot.studio.prompt_diff'],
onUpdateSuccess: reloadList,
onDiff: ({ libraryId }) => {
recordRef.current = {
res_id: libraryId,
};
openSelectIntelligenceModal();
},
});
// 删除
const { run: delPrompt } = useRequest(
(promptId: string) =>
PlaygroundApi.DeletePromptResource({
prompt_resource_id: promptId,
}),
{
manual: true,
onSuccess: () => {
reloadList();
Toast.success(I18n.t('Delete_success'));
},
},
);
return {
modals: (
<>
{selectIntelligenceModal}
{promptConfiguratorModal}
</>
),
config: {
typeFilter: {
label: I18n.t('library_resource_type_prompt'),
value: ResType.Prompt,
},
renderCreateMenu: () => (
<Menu.Item
data-testid="workspace.library.header.create.prompt"
icon={<IconCozLightbulb />}
onClick={() => {
sendTeaEvent(EVENT_NAMES.widget_create_click, {
source: 'menu_bar',
workspace_type: isPersonalSpace
? 'personal_workspace'
: 'team_workspace',
});
openCreatePrompt({
mode: 'create',
});
}}
>
{I18n.t('creat_new_prompt_prompt')}
</Menu.Item>
),
target: [ResType.Prompt],
onItemClick: (record: ResourceInfo) => {
recordRef.current = record;
const canEdit = record.actions?.find(
action => action.key === ActionKey.Edit,
)?.enable;
openCreatePrompt({
mode: 'info',
canEdit,
editId: record.res_id || '',
});
},
renderActions: (libraryResource: ResourceInfo) => (
<TableAction
deleteProps={{
disabled: !libraryResource.actions?.find(
action => action.key === ActionKey.Delete,
)?.enable,
deleteDesc: I18n.t('prompt_resource_delete_describ'),
handler: () => {
delPrompt(libraryResource.res_id || '');
},
}}
editProps={{
disabled: !libraryResource.actions?.find(
action => action.key === ActionKey.Edit,
)?.enable,
handler: () => {
openCreatePrompt({
mode: 'edit',
editId: libraryResource.res_id || '',
});
},
}}
actionList={getCommonActions?.(libraryResource)}
/>
),
},
};
};

View File

@@ -0,0 +1,126 @@
/*
* 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 { useWorkflowResourceAction } from '@coze-workflow/components';
import { useUserInfo } from '@coze-foundation/account-adapter';
import { ResType, WorkflowMode } from '@coze-arch/idl/plugin_develop';
import { I18n } from '@coze-arch/i18n';
import { IconCozChat, IconCozWorkflow } from '@coze-arch/coze-design/icons';
import { Menu, Tag } from '@coze-arch/coze-design';
import { BaseLibraryItem } from '../../components/base-library-item';
import WorkflowDefaultIcon from '../../assets/workflow_default_icon.png';
import ImageFlowDefaultIcon from '../../assets/image_flow_default_icon.png';
import { type UseEntityConfigHook } from './types';
const defaultIconMap: { [key in ResType]?: string } = {
[ResType.Workflow]: WorkflowDefaultIcon,
[ResType.Imageflow]: ImageFlowDefaultIcon,
};
export const useWorkflowConfig: UseEntityConfigHook = ({
spaceId,
reloadList,
getCommonActions,
}) => {
const userInfo = useUserInfo();
const {
workflowResourceModals,
handleWorkflowResourceClick,
renderWorkflowResourceActions,
openCreateModal,
} = useWorkflowResourceAction({
spaceId,
userId: userInfo?.user_id_str,
refreshPage: reloadList,
getCommonActions,
});
return {
modals: workflowResourceModals,
config: {
typeFilter: {
label: I18n.t('library_resource_type_workflow'),
value: ResType.Workflow,
},
parseParams: params => {
// 工作流图像流合并之后 选中工作流需要同时也拉取出图像流
if (params?.res_type_filter?.[0] === ResType.Workflow) {
return {
...params,
is_get_imageflow: true,
};
}
return params;
},
renderCreateMenu: () => (
<>
<Menu.Item
data-testid="workspace.library.header.create.workflow"
icon={<IconCozWorkflow />}
onClick={() => {
openCreateModal(WorkflowMode.Workflow);
}}
>
{I18n.t('library_resource_type_workflow')}
</Menu.Item>
{/* 社区版本暂时不支持对话流 */}
{!IS_OPEN_SOURCE ? (
<Menu.Item
data-testid="workspace.library.header.create.chatflow"
icon={<IconCozChat />}
onClick={() => {
openCreateModal(WorkflowMode.ChatFlow);
}}
>
{I18n.t('wf_chatflow_76')}
</Menu.Item>
) : null}
</>
),
target: [ResType.Workflow, ResType.Imageflow],
onItemClick: handleWorkflowResourceClick,
renderItem: item => (
<BaseLibraryItem
resourceInfo={item}
defaultIcon={
item.res_type !== undefined
? defaultIconMap[item.res_type]
: undefined
}
tag={
item.collaboration_enable === true ? (
<Tag
data-testid="workspace.library.item.tag"
color="brand"
size="mini"
className="flex-shrink-0 flex-grow-0"
>
{I18n.t('library_filter_tags_collaboration')}
</Tag>
) : null
}
/>
),
renderResType: item =>
item.res_type === ResType.Workflow &&
item.res_sub_type === WorkflowMode.ChatFlow
? I18n.t('wf_chatflow_76')
: I18n.t('library_resource_type_workflow'),
renderActions: renderWorkflowResourceActions,
},
};
};

View File

@@ -0,0 +1,50 @@
.selected-params {
border:1px solid var(--semi-color-focus-border);
outline:0
}
.layout-content {
height: calc(100%);
:global(.coz-layout-header) {
padding-bottom: 16px;
}
}
.dropdown-options-6 {
:global(.semi-cascader-option-lists) {
height: 208px;
}
}
.dropdown-options-7 {
:global(.semi-cascader-option-lists) {
height: 242px;
}
}
.dropdown-options-8 {
:global(.semi-cascader-option-lists) {
height: 276px;
}
}
.dropdown-options-9 {
:global(.semi-cascader-option-lists) {
height: 310px;
}
}
.layout-header {
.select {
@apply shrink-0 !important;
min-width: 128px;
}
.cascader{
@apply shrink-0 !important;
min-width: 128px;
}
}

View File

@@ -0,0 +1,299 @@
/*
* 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 { forwardRef, useImperativeHandle } from 'react';
import classNames from 'classnames';
import { useInfiniteScroll } from 'ahooks';
import { I18n } from '@coze-arch/i18n';
import {
Table,
Select,
Search,
Layout,
Cascader,
Space,
} from '@coze-arch/coze-design';
import { renderHtmlTitle } from '@coze-arch/bot-utils';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import {
type ResType,
type LibraryResourceListRequest,
type ResourceInfo,
} from '@coze-arch/bot-api/plugin_develop';
import { PluginDevelopApi } from '@coze-arch/bot-api';
import { highlightFilterStyle } from '@/constants/filter-style';
import { WorkspaceEmpty } from '@/components/workspace-empty';
import { type ListData, type BaseLibraryPageProps } from './types';
import { useGetColumns } from './hooks/use-columns';
import { useCachedQueryParams } from './hooks/use-cached-query-params';
import {
eventLibraryType,
getScopeOptions,
getStatusOptions,
LIBRARY_PAGE_SIZE,
} from './consts';
import { LibraryHeader } from './components/library-header';
import s from './index.module.less';
export { useDatabaseConfig } from './hooks/use-entity-configs/use-database-config';
export { usePluginConfig } from './hooks/use-entity-configs/use-plugin-config';
export { useWorkflowConfig } from './hooks/use-entity-configs/use-workflow-config';
export { usePromptConfig } from './hooks/use-entity-configs/use-prompt-config';
export { useKnowledgeConfig } from './hooks/use-entity-configs/use-knowledge-config';
export { type LibraryEntityConfig } from './types';
export { type UseEntityConfigHook } from './hooks/use-entity-configs/types';
export { BaseLibraryItem } from './components/base-library-item';
export const BaseLibraryPage = forwardRef<
{ reloadList: () => void },
BaseLibraryPageProps
>(
// eslint-disable-next-line @coze-arch/max-line-per-function
({ spaceId, isPersonalSpace = true, entityConfigs }, ref) => {
const { params, setParams, resetParams, hasFilter, ready } =
useCachedQueryParams({
spaceId,
});
const listResp = useInfiniteScroll<ListData>(
async prev => {
if (!ready) {
return {
list: [],
nextCursorId: undefined,
hasMore: false,
};
}
// 允许业务定制请求参数
const resp = await PluginDevelopApi.LibraryResourceList(
entityConfigs.reduce<LibraryResourceListRequest>(
(res, config) => config.parseParams?.(res) ?? res,
{
...params,
cursor: prev?.nextCursorId,
space_id: spaceId,
size: LIBRARY_PAGE_SIZE,
},
),
);
return {
list: resp?.resource_list || [],
nextCursorId: resp?.cursor,
hasMore: !!resp?.has_more,
};
},
{
reloadDeps: [params, spaceId],
},
);
useImperativeHandle(ref, () => ({
reloadList: listResp.reload,
}));
const columns = useGetColumns({
entityConfigs,
reloadList: listResp.reload,
isPersonalSpace,
});
const typeFilterData = [
{ label: I18n.t('library_filter_tags_all_types'), value: -1 },
...entityConfigs.map(item => item.typeFilter).filter(filter => !!filter),
];
const scopeOptions = getScopeOptions();
const statusOptions = getStatusOptions();
return (
<Layout
className={s['layout-content']}
title={renderHtmlTitle(I18n.t('navigation_workspace_library'))}
>
<Layout.Header className={classNames(s['layout-header'], 'pb-0')}>
<div className="w-full">
<LibraryHeader entityConfigs={entityConfigs} />
<div className="flex items-center justify-between">
<Space>
<Cascader
data-testid="workspace.library.filter.type"
className={s.cascader}
style={
params?.res_type_filter?.[0] !== -1
? highlightFilterStyle
: {}
}
dropdownClassName="[&_.semi-cascader-option-lists]:h-fit"
showClear={false}
value={params.res_type_filter}
treeData={typeFilterData}
onChange={v => {
const typeFilter = typeFilterData.find(
item =>
item.value === ((v as Array<number>)?.[0] as number),
);
sendTeaEvent(EVENT_NAMES.workspace_action_front, {
space_id: spaceId,
space_type: isPersonalSpace ? 'personal' : 'teamspace',
tab_name: 'library',
action: 'filter',
filter_type: 'types',
filter_name: typeFilter?.filterName ?? typeFilter?.label,
});
setParams(prev => ({
...prev,
res_type_filter: v as Array<number>,
}));
}}
/>
{!isPersonalSpace ? (
<Select
data-testid="workspace.library.filter.user"
className={classNames(s.select)}
style={
params?.user_filter !== 0 ? highlightFilterStyle : {}
}
showClear={false}
value={params.user_filter}
optionList={scopeOptions}
onChange={v => {
sendTeaEvent(EVENT_NAMES.workspace_action_front, {
space_id: spaceId,
space_type: isPersonalSpace ? 'personal' : 'teamspace',
tab_name: 'library',
action: 'filter',
filter_type: 'creators',
filter_name: scopeOptions.find(
item =>
item.value ===
((v as Array<number>)?.[0] as number),
)?.label,
});
setParams(prev => ({
...prev,
user_filter: v as number,
}));
}}
/>
) : null}
<Select
data-testid="workspace.library.filter.status"
className={s.select}
style={
params?.publish_status_filter !== 0
? highlightFilterStyle
: {}
}
showClear={false}
value={params.publish_status_filter}
optionList={statusOptions}
onChange={v => {
sendTeaEvent(EVENT_NAMES.workspace_action_front, {
space_id: spaceId,
space_type: isPersonalSpace ? 'personal' : 'teamspace',
tab_name: 'library',
action: 'filter',
filter_type: 'status',
filter_name: statusOptions.find(
item =>
item.value === ((v as Array<number>)?.[0] as number),
)?.label,
});
setParams(prev => ({
...prev,
publish_status_filter: v as number,
}));
}}
/>
</Space>
<Search
data-testid="workspace.library.filter.name"
className="!min-w-min"
style={params.name ? highlightFilterStyle : {}}
showClear={true}
width={200}
loading={listResp.loading}
placeholder={I18n.t('workspace_library_search')}
value={params.name}
onSearch={v => {
sendTeaEvent(EVENT_NAMES.search_front, {
full_url: window.location.href,
source: 'library',
search_word: v,
});
setParams(prev => ({
...prev,
name: v,
}));
}}
/>
</div>
</div>
</Layout.Header>
<Layout.Content>
<Table
data-testid="workspace.library.table"
offsetY={178}
tableProps={{
loading: listResp.loading,
dataSource: listResp.data?.list,
columns,
// 整行点击
onRow: (record?: ResourceInfo) => {
if (
!record ||
record.res_type === undefined ||
record.detail_disable
) {
return {};
}
return {
onClick: () => {
sendTeaEvent(EVENT_NAMES.workspace_action_front, {
space_id: spaceId,
space_type: isPersonalSpace ? 'personal' : 'teamspace',
tab_name: 'library',
action: 'click',
id: record.res_id,
name: record.name,
type:
record.res_type && eventLibraryType[record.res_type],
});
entityConfigs
.find(c => c.target.includes(record.res_type as ResType))
?.onItemClick(record);
},
};
},
}}
empty={
<WorkspaceEmpty onClear={resetParams} hasFilter={hasFilter} />
}
enableLoad
loadMode="cursor"
strictDataSourceProp
hasMore={listResp.data?.hasMore}
onLoad={listResp.loadMore}
/>
</Layout.Content>
</Layout>
);
},
);

View File

@@ -0,0 +1,95 @@
/*
* 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 ReactNode } from 'react';
import { type CascaderData } from '@coze-arch/coze-design';
import {
type LibraryResourceListRequest,
type ResType,
type ResourceInfo,
} from '@coze-arch/bot-api/plugin_develop';
export interface LibraryEntityConfig {
/**
* 资源类型筛选器配置,传入级联选择器的数据类型
**/
typeFilter?: CascaderData & ({ filterName: string } | { label: string });
/**
* 允许各个业务定制请求参数的格式化逻辑,避免特化逻辑侵入到底层组件中
* @param params 原始的 query 参数
* @returns 格式化后的 query 参数
*/
parseParams?: (
params: LibraryResourceListRequest,
) => LibraryResourceListRequest;
/**
* 渲染创建菜单
* @param params 相关参数
* @params params.spaceId 空间 ID
* @params params.isPersonalSpace 是否是个人空间
* @params params.reloadList 刷新列表 API
* @returns 渲染结果
*/
renderCreateMenu?: () => ReactNode;
// #region 表格配置
/**
* 匹配数据项是否由当前配置控制渲染
*/
target: ResType[];
/**
* 表格行点击事件回调,一般用于打开详情弹窗或者跳转详情页
* @param item 点击行数据;
* @returns void;
*/
onItemClick: (item: ResourceInfo) => void;
/**
* 渲染表格资源信息列内容,不传则默认使用通用组件进行渲染
* @param item 行数据
* @returns 渲染结果
*/
renderItem?: (item: ResourceInfo) => ReactNode;
/**
* 渲染资源类型文案,缺省会使用 typeFilter 中的 label
* @param resType
* @returns
*/
renderResType?: (item: ResourceInfo) => string | undefined;
/**
* 渲染表格操作列内容
* @param item 行数据
* @param reloadList 刷新列表 API
* @returns 渲染结果
*/
renderActions: (item: ResourceInfo, reloadList: () => void) => ReactNode;
// #endregion 表格配置
}
export interface ListData {
list: ResourceInfo[];
hasMore: boolean;
nextCursorId: string | undefined;
}
export interface BaseLibraryPageProps {
spaceId: string;
isPersonalSpace?: boolean;
entityConfigs: LibraryEntityConfig[];
}

View File

@@ -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 { MockSetList } from '@coze-agent-ide/bot-plugin/page';
const Plugin = ({
toolID: propsToolID,
}: {
pluginID: string;
spaceID: string;
toolID: string;
}) => <MockSetList toolID={propsToolID} />;
export default Plugin;

View File

@@ -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 { MockSetDetail } from '@coze-agent-ide/bot-plugin/page';
const Plugin = ({
pluginID,
spaceID,
toolID: propsToolID,
mocksetID: propsMocksetID,
}: {
pluginID: string;
spaceID: string;
toolID: string;
mocksetID: string;
}) => (
<MockSetDetail
toolID={propsToolID}
mocksetID={propsMocksetID}
pluginID={pluginID}
spaceID={spaceID}
/>
);
export default Plugin;

View File

@@ -0,0 +1,19 @@
/*
* 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 { PluginDetailPage } from '@coze-agent-ide/bot-plugin/page';
export default PluginDetailPage;

View File

@@ -0,0 +1,19 @@
/*
* 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 { ToolDetailPage } from '@coze-agent-ide/bot-plugin/page';
export default ToolDetailPage;

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' />

View File

@@ -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.
*/
import qs from 'qs';
import { pick } from 'lodash-es';
import { type PluginNavType } from '@coze-studio/bot-plugin-store/src/context';
/**
* 比较两个对象是否相等,只比较指定的 key通过 JSON.stringify 实现
* @param obj1 对象1
* @param obj2 对象2
* @param keys 需要比较的 key
* @returns 是否相等
*/
export function compareObjects<T>(
obj1: T,
obj2: T,
keys: (keyof T)[],
): boolean {
const subset1 = pick(obj1, keys);
const subset2 = pick(obj2, keys);
return JSON.stringify(subset1) === JSON.stringify(subset2);
}
export function resourceNavigate(
navBase: string,
pluginID: string,
navigate: Function,
): PluginNavType {
return {
// eslint-disable-next-line max-params
toResource: (resource, rid, query, opts) =>
rid
? navigate(`${navBase}/${resource}/${rid}?${qs.stringify(query)}`, opts)
: '',
tool: (toolID, query, opts) =>
navigate(
`${navBase}/plugin/${pluginID}/tool/${toolID}?${qs.stringify(query)}`,
opts,
),
mocksetList: (toolID, query, opts) =>
navigate(
`${navBase}/plugin/${pluginID}/tool/${toolID}/plugin-mock-set?${qs.stringify(query)}`,
opts,
),
// eslint-disable-next-line max-params
mocksetDetail: (toolID, mocksetID, query, opts) =>
navigate(
`${navBase}/plugin/${pluginID}/tool/${toolID}/plugin-mock-set/${mocksetID}?${qs.stringify(query)}`,
opts,
),
cloudIDE: (query, opts) =>
navigate(
`${navBase}/plugin/${pluginID}/cloud-tool?${qs.stringify(query)}`,
opts,
),
};
}

View File

@@ -0,0 +1,159 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
},
"types": [],
"strictNullChecks": true,
"noImplicitAny": true,
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.build.tsbuildinfo"
},
"include": ["src"],
"references": [
{
"path": "../../../agent-ide/bot-plugin/entry/tsconfig.build.json"
},
{
"path": "../../../agent-ide/space-bot/tsconfig.build.json"
},
{
"path": "../../../arch/bot-api/tsconfig.build.json"
},
{
"path": "../../../arch/bot-error/tsconfig.build.json"
},
{
"path": "../../../arch/bot-flags/tsconfig.build.json"
},
{
"path": "../../../arch/bot-space-api/tsconfig.build.json"
},
{
"path": "../../../arch/bot-store/tsconfig.build.json"
},
{
"path": "../../../arch/bot-tea/tsconfig.build.json"
},
{
"path": "../../../arch/bot-typings/tsconfig.build.json"
},
{
"path": "../../../arch/bot-utils/tsconfig.build.json"
},
{
"path": "../../../arch/i18n/tsconfig.build.json"
},
{
"path": "../../../arch/idl/tsconfig.build.json"
},
{
"path": "../../../arch/logger/tsconfig.build.json"
},
{
"path": "../../../arch/report-events/tsconfig.build.json"
},
{
"path": "../../bot-utils/tsconfig.build.json"
},
{
"path": "../../../common/biz-components/tsconfig.build.json"
},
{
"path": "../../../common/chat-area/utils/tsconfig.build.json"
},
{
"path": "../../../common/coze-mitt/tsconfig.build.json"
},
{
"path": "../../../common/prompt-kit/adapter/tsconfig.build.json"
},
{
"path": "../../../common/prompt-kit/main/tsconfig.build.json"
},
{
"path": "../../../community/component/tsconfig.build.json"
},
{
"path": "../../../components/bot-semi/tsconfig.build.json"
},
{
"path": "../../components/tsconfig.build.json"
},
{
"path": "../../../../config/eslint-config/tsconfig.build.json"
},
{
"path": "../../../../config/stylelint-config/tsconfig.build.json"
},
{
"path": "../../../../config/ts-config/tsconfig.build.json"
},
{
"path": "../../../../config/vitest-config/tsconfig.build.json"
},
{
"path": "../../../data/common/utils/tsconfig.build.json"
},
{
"path": "../../../data/knowledge/common/stores/tsconfig.build.json"
},
{
"path": "../../../data/knowledge/knowledge-ide-adapter/tsconfig.build.json"
},
{
"path": "../../../data/knowledge/knowledge-ide-base/tsconfig.build.json"
},
{
"path": "../../../data/knowledge/knowledge-modal-adapter/tsconfig.build.json"
},
{
"path": "../../../data/knowledge/knowledge-resource-processor-adapter/tsconfig.build.json"
},
{
"path": "../../../data/knowledge/knowledge-resource-processor-base/tsconfig.build.json"
},
{
"path": "../../../data/knowledge/knowledge-resource-processor-core/tsconfig.build.json"
},
{
"path": "../../../data/memory/database-v2-main/tsconfig.build.json"
},
{
"path": "../../../foundation/account-adapter/tsconfig.build.json"
},
{
"path": "../../../foundation/enterprise-store-adapter/tsconfig.build.json"
},
{
"path": "../../../foundation/layout/tsconfig.build.json"
},
{
"path": "../../../foundation/local-storage/tsconfig.build.json"
},
{
"path": "../../premium/premium-components-adapter/tsconfig.build.json"
},
{
"path": "../../premium/premium-store-adapter/tsconfig.build.json"
},
{
"path": "../project-entity-adapter/tsconfig.build.json"
},
{
"path": "../../stores/bot-plugin/tsconfig.build.json"
},
{
"path": "../../user-store/tsconfig.build.json"
},
{
"path": "../../../workflow/base/tsconfig.build.json"
},
{
"path": "../../../workflow/components/tsconfig.build.json"
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"composite": true
},
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.misc.json"
}
],
"exclude": ["**/*"]
}

View File

@@ -0,0 +1,21 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"include": ["__tests__", "stories", "vitest.config.ts", "tailwind.config.ts"],
"exclude": ["./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
],
"compilerOptions": {
"rootDir": "./",
"outDir": "./dist",
"paths": {
"@/*": ["./src/*"]
},
"types": ["vitest/globals"],
"strictNullChecks": true,
"noImplicitAny": true
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineConfig } from '@coze-arch/vitest-config';
export default defineConfig({
dirname: __dirname,
preset: 'web',
});

View File

@@ -0,0 +1,5 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
});

View File

@@ -0,0 +1,16 @@
# @coze-studio/project-entity-adapter
> Project template for react component with storybook.
## Features
- [x] eslint & ts
- [x] esm bundle
- [x] umd bundle
- [x] storybook
## Commands
- init: `rush update`
- dev: `npm run dev`
- build: `npm run build`

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["./dist"]
}
]
}

View File

@@ -0,0 +1,7 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {},
});

View File

@@ -0,0 +1,44 @@
{
"name": "@coze-studio/project-entity-adapter",
"version": "0.0.1",
"description": "project entity crud",
"license": "Apache-2.0",
"author": "gaoyuanhan.duty@bytedance.com",
"maintainers": [],
"main": "src/index.ts",
"scripts": {
"build": "exit 0",
"dev": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"@coze-studio/project-entity-base": "workspace:*",
"@coze-arch/idl": "workspace:*"
},
"devDependencies": {
"@coze-arch/bot-typings": "workspace:*",
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/stylelint-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/lodash-es": "^4.17.10",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@vitest/coverage-v8": "~3.0.5",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"stylelint": "^15.11.0",
"typescript": "~5.8.2",
"vite-plugin-svgr": "~3.3.0",
"vitest": "~3.0.5"
},
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0"
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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 {
useDeleteIntelligence,
type ProjectFormValues,
type UpdateProjectSuccessCallbackParam,
type CreateProjectHookProps,
type CopyProjectSuccessCallbackParam,
type ModifyUploadValueType,
type RequireCopyProjectRequest,
type DeleteIntelligenceParam,
} from '@coze-studio/project-entity-base';
import { type ReactNode } from 'react';
import { type DraftProjectCopyRequest } from '@coze-arch/idl/intelligence_api';
import {
useCreateProjectModalBase,
useUpdateProjectModalBase,
useCopyProjectModalBase,
type ProjectFormValues,
type UpdateProjectSuccessCallbackParam,
type CreateProjectHookProps,
type CopyProjectSuccessCallbackParam,
type ModifyUploadValueType,
type RequireCopyProjectRequest,
} from '@coze-studio/project-entity-base';
export const useCreateProjectModal = (
params: CreateProjectHookProps,
): {
modalContextHolder: ReactNode;
createProject: () => void;
} => useCreateProjectModalBase(params);
export const useUpdateProjectModal = (params: {
onSuccess?: (param: UpdateProjectSuccessCallbackParam) => void;
}): {
modalContextHolder: ReactNode;
openModal: (params: { initialValue: ProjectFormValues }) => void;
} => useUpdateProjectModalBase(params);
export const useCopyProjectModal = (params: {
onSuccess?: (param: CopyProjectSuccessCallbackParam) => void;
}): {
modalContextHolder: ReactNode;
openModal: (param: {
initialValue: ModifyUploadValueType<
RequireCopyProjectRequest<DraftProjectCopyRequest>
>;
}) => void;
} => useCopyProjectModalBase(params);

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' />

View File

@@ -0,0 +1,37 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"compilerOptions": {
"types": [],
"strictNullChecks": true,
"noImplicitAny": true,
"noUncheckedIndexedAccess": true,
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.build.tsbuildinfo"
},
"include": ["src"],
"references": [
{
"path": "../../../arch/bot-typings/tsconfig.build.json"
},
{
"path": "../../../arch/idl/tsconfig.build.json"
},
{
"path": "../../../../config/eslint-config/tsconfig.build.json"
},
{
"path": "../../../../config/stylelint-config/tsconfig.build.json"
},
{
"path": "../../../../config/ts-config/tsconfig.build.json"
},
{
"path": "../../../../config/vitest-config/tsconfig.build.json"
},
{
"path": "../project-entity-base/tsconfig.build.json"
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"exclude": ["**/*"],
"compilerOptions": {
"composite": true
},
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.misc.json"
}
]
}

View File

@@ -0,0 +1,19 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"include": ["__tests__", "stories", "vitest.config.ts", "tailwind.config.ts"],
"exclude": ["./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
],
"compilerOptions": {
"rootDir": "./",
"outDir": "./dist",
"types": ["vitest/globals"],
"strictNullChecks": true,
"noImplicitAny": true,
"noUncheckedIndexedAccess": true
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineConfig } from '@coze-arch/vitest-config';
export default defineConfig({
dirname: __dirname,
preset: 'web',
});

View File

@@ -0,0 +1,31 @@
import { mergeConfig } from 'vite';
import svgr from 'vite-plugin-svgr';
/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.tsx'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-onboarding',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
viteFinal: config =>
mergeConfig(config, {
plugins: [
svgr({
svgrOptions: {
native: false,
},
}),
],
}),
};
export default config;

View File

@@ -0,0 +1,14 @@
/** @type { import('@storybook/react').Preview } */
const preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;

View File

@@ -0,0 +1,5 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
});

View File

@@ -0,0 +1,16 @@
# @coze-studio/project-entity-base
> Project template for react component with storybook.
## Features
- [x] eslint & ts
- [x] esm bundle
- [x] umd bundle
- [x] storybook
## Commands
- init: `rush update`
- dev: `npm run dev`
- build: `npm run build`

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["./dist"]
}
]
}

View File

@@ -0,0 +1,7 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {},
});

View File

@@ -0,0 +1,62 @@
{
"name": "@coze-studio/project-entity-base",
"version": "0.0.1",
"description": "project entity crud",
"license": "Apache-2.0",
"author": "gaoyuanhan.duty@bytedance.com",
"maintainers": [],
"main": "src/index.tsx",
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"@coze-agent-ide/bot-input-length-limit": "workspace:*",
"@coze-agent-ide/space-bot": "workspace:*",
"@coze-arch/bot-api": "workspace:*",
"@coze-arch/bot-studio-store": "workspace:*",
"@coze-arch/bot-tea": "workspace:*",
"@coze-arch/bot-utils": "workspace:*",
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
"@coze-arch/i18n": "workspace:*",
"@coze-arch/idl": "workspace:*",
"@coze-common/biz-components": "workspace:*",
"@coze-common/chat-area-utils": "workspace:*",
"@coze-foundation/local-storage": "workspace:*",
"@coze-studio/components": "workspace:*",
"@coze-studio/entity-adapter": "workspace:*",
"@douyinfe/semi-illustrations": "^2.36.0",
"ahooks": "^3.7.8",
"classnames": "^2.3.2",
"lodash-es": "^4.17.21",
"react-markdown": "^8.0.3",
"zustand": "^4.4.7"
},
"devDependencies": {
"@coze-arch/bot-typings": "workspace:*",
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/stylelint-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/lodash-es": "^4.17.10",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@vitest/coverage-v8": "~3.0.5",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"stylelint": "^15.11.0",
"typescript": "~5.8.2",
"vite-plugin-svgr": "~3.3.0",
"vitest": "~3.0.5"
},
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0"
}
}

Some files were not shown because too many files have changed in this diff Show More