当前位置: 首页 > ops >正文

Coze源码分析-工作空间-项目开发-前端源码

前言

本文将深入分析Coze Studio项目中用户登录后进入工作空间查看和管理项目的前端实现,通过源码解读来理解工作空间项目开发功能的架构设计和技术实现。Coze Studio采用了现代化的React + TypeScript技术栈,结合微前端架构和模块化设计,为用户提供了高效的AI智能体开发环境。

工作空间作为用户的核心工作区域,承载着项目管理、智能体开发、资源配置等关键功能。本文将从路由配置开始,逐层深入到组件架构、数据流管理、API设计等各个层面,全面解析工作空间项目开发功能的技术实现。

项目架构概览

整体架构设计

Coze Studio前端采用了基于Rush.js的Monorepo架构,将工作空间相关功能划分为以下几个核心包:

frontend/packages/
├── studio/workspace/           # 工作空间核心模块
│   ├── entry-adapter/         # 工作空间适配器层
│   ├── entry-base/            # 工作空间基础组件
│   ├── project-entity-adapter/ # 项目实体适配器
│   ├── project-entity-base/   # 项目实体基础功能
│   └── project-publish/       # 项目发布功能
├── foundation/                # 基础设施层
│   ├── space-store/          # 空间状态管理
│   ├── space-ui-adapter/     # 空间UI适配器
│   └── space-ui-base/        # 空间UI基础组件
└── arch/                     # 架构层├── idl/                  # 接口定义层└── bot-api/              # API调用层

技术栈组成

  • 框架: React 18 + TypeScript
  • 路由: React Router v6
  • 状态管理: Zustand
  • UI组件: @coze-arch/coze-design
  • 数据请求: Axios + 自定义API层
  • 国际化: @coze-arch/i18n
  • 构建工具: Rsbuild

路由系统设计

主路由配置

文件位置:frontend/apps/coze-studio/src/routes/index.tsx

核心代码:

export const router: ReturnType<typeof createBrowserRouter> =createBrowserRouter([{path: '/',Component: Layout,errorElement: <GlobalError />,children: [{index: true,element: <Navigate to="/space" replace />,},// 工作空间路由{path: 'space',Component: SpaceLayout,loader: () => ({hasSider: true,requireAuth: true,subMenu: spaceSubMenu,menuKey: BaseEnum.Space,}),children: [{path: ':space_id',Component: SpaceIdLayout,children: [{index: true,element: <Navigate to="develop" replace />,},// 项目开发页面{path: 'develop',Component: Develop,loader: () => ({subMenuKey: SpaceSubModuleEnum.DEVELOP,}),},// 智能体IDE{path: 'bot/:bot_id',Component: AgentIDELayout,// ...},// 项目IDE{path: 'project-ide/:project_id/*',Component: ProjectIDE,// ...},// 资源库{path: 'library',Component: Library,// ...},],},],},],},]);

代码作用:
这段代码是Coze Studio应用的 核心路由配置 ,使用React Router v6的 createBrowserRouter 创建了一个层次化的路由系统。主要作用包括:

路由结构设计
根路由 ( / ) :

  • 使用 Layout 组件作为整体布局容器
  • 配置了 GlobalError 作为全局错误边界
  • 默认重定向到 /space 工作空间

工作空间路由 ( /space ) :

  • 使用 SpaceLayout 组件提供工作空间布局
  • 通过 loader 配置页面属性:侧边栏显示、身份验证要求、子菜单等
  • 支持嵌套的子路由结构

具体空间路由 ( /space/:space_id ) :

  • 使用动态参数 :space_id 标识具体的工作空间
  • SpaceIdLayout 组件管理特定空间的布局
  • 默认重定向到 develop 开发页面

这种设计的优势:

  • 层次清晰:每一层负责不同的布局和权限控制
  • 参数传递:通过URL参数自然传递spaceId等关键信息
  • 懒加载:支持按需加载不同功能模块
  • 权限控制:在loader中统一处理认证和权限检查

核心组件分析

SpaceLayout组件

文件位置:frontend/packages/foundation/space-ui-adapter/src/components/space-layout/index.tsx

核心代码:

export const SpaceLayout = () => {const { space_id } = useParams();const { loading, spaceListLoading, spaceList } = useInitSpace(space_id);if (!loading && !spaceListLoading && spaceList.length === 0) {return (<EmptyclassName="h-full justify-center w-full"image={<IconCozIllusAdd width="160" height="160" />}title={I18n.t('enterprise_workspace_no_space_title')}description={I18n.t('enterprise_workspace_default_tips1_nonspace')}/>);}if (loading) {return null;}return <Outlet />;
};

组件职责

  • 空间初始化:通过useInitSpace hook初始化工作空间
  • 状态处理:处理加载状态和空状态
  • 布局渲染:为子路由提供布局容器

Develop组件(项目开发页面)

文件位置:frontend/packages/studio/workspace/entry-adapter/src/pages/develop/index.tsx

核心代码:

export const Develop: FC<DevelopProps> = ({ spaceId }) => {const isPersonal = useSpaceStore(state => state.space.space_type === SpaceType.Personal,);// 关键词搜索和筛选const [filterParams, setFilterParams, debouncedSetSearchValue] =useCachedQueryParams();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,},});return (<Layout><Header><HeaderTitle><span>{I18n.t('workspace_develop')}</span></HeaderTitle><HeaderActions><Button icon={<IconCozPlus />} onClick={actions.createIntelligence}>{I18n.t('workspace_create')}</Button></HeaderActions></Header>{/* 渲染项目列表 */}</Layout>);
};

组件特点

  • 状态管理:使用useSpaceStore获取当前空间信息
  • 参数缓存:通过useCachedQueryParams缓存搜索参数
  • 无限滚动:支持大量项目的高效展示
  • 实时搜索:支持关键词、类型、创建者等多维度筛选

BotCard组件(项目卡片)

文件位置:frontend/packages/studio/workspace/entry-base/src/pages/develop/components/bot-card/index.tsx

核心代码:

export interface BotCardProps {intelligenceInfo: IntelligenceData;timePrefixType?: 'recentOpen' | 'publish' | 'edit';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;
}export const BotCard: React.FC<BotCardProps> = ({intelligenceInfo,timePrefixType,onClick,onDelete,onUpdateIntelligenceInfo,// ... 其他props
}) => {const navigate = useNavigate();const {basic_info,type,permission_info: { in_collaboration, can_delete } = {},publish_info: { publish_time, connectors, has_published } = {},owner_info,favorite_info: { is_fav } = {},} = intelligenceInfo;const { id, name, icon_url, space_id, description, update_time, status } =basic_info ?? {};const handleCardClick = () => {if (onClick?.()) {return;}if (type === IntelligenceType.Bot) {navigate(`/space/${space_id}/bot/${id}`);} else if (type === IntelligenceType.Project) {navigate(`/space/${space_id}/project-ide/${id}`);}};return (<CardclassName="bot-card"hoverableonMouseEnter={() => setIsHovered(true)}onMouseLeave={() => setIsHovered(false)}onClick={handleCardClick}><div className="card-header"><Avatar src={intelligence.icon_url} size={40} /><div className="card-info"><div className="card-title">{intelligence.name}</div><div className="card-description">{intelligence.description}</div></div>{isHovered && (<Dropdown menu={{ items: actions }} trigger={['click']}><Button icon={<MoreOutlined />} type="text" /></Dropdown>)}</div><div className="card-footer"><StatusBadge status={intelligence.status} /><div className="card-meta"><span>{intelligence.owner_name}</span><span>{formatTime(intelligence.update_time)}</span></div></div></Card>);
};

组件功能

  • 交互设计:悬停显示操作菜单,点击进入详情
  • 状态展示:显示项目状态、所有者、更新时间等信息
  • 事件追踪:集成埋点系统,追踪用户行为
  • 动作支持:支持收藏、复制、删除等操作

业务适配层

useInitSpace Hook

文件位置:frontend/packages/foundation/space-ui-base/src/hooks/use-init-space.ts

export const useInitSpace = ({spaceId,fetchSpacesWithSpaceId,isReady,
}: {spaceId?: string;fetchSpacesWithSpaceId?: (spaceId: string) => Promise<unknown>;isReady?: boolean;
} = {}) => {const [isError, setIsError] = useState<boolean>(false);const navigate = useNavigate();const capture = useErrorHandler();const { space, spaceListLoading, spaceList } = useSpaceStore(useShallow(store =>({space: store.space,spaceListLoading: store.loading,spaceList: store.spaceList,} as const),),);useEffect(() => {(async (spaceId?: string) => {try {if (!isReady) {return;}// 如果没有指定spaceId,跳转到后备空间的项目开发子路由if (!spaceId) {// 拉取空间列表await useSpaceStore.getState().fetchSpaces(true);// 获取个人空间IDconst personalSpaceID = useSpaceStore.getState().getPersonalSpaceID();// 空间列表中的第一个空间const firstSpaceID = useSpaceStore.getState().spaceList[0]?.id;// 未指定spaceId时的后备spaceIdconst fallbackSpaceID = personalSpaceID ?? firstSpaceID ?? '';// 检查指定的spaceId是否可访问const { checkSpaceID } = useSpaceStore.getState();// 没有工作空间,提示创建if (!fallbackSpaceID) {Toast.warning(I18n.t('enterprise_workspace_default_tips2_toast'));} else {// 获取后备的跳转URLconst targetURL = await getFallbackWorkspaceURL(fallbackSpaceID,'develop',checkSpaceID,);// 跳转navigate(targetURL);}} else {// 拉取空间列表await fetchSpacesWithSpaceId?.(spaceId);if (!useSpaceStore.getState().checkSpaceID(spaceId)) {// 当在空间列表中找不到该空间id时抛出错误capture(new CustomError(ReportEventNames.errorPath, 'space id error', {customGlobalErrorConfig: {title: I18n.t('workspace_no_permission_access'),subtitle:'You do not have permission to access this space or the space ID does not exist',},}),);} else {// 更新space store中的spaceIduseSpaceStore.getState().setSpace(spaceId);}}} catch (e) {reporter.error({message: 'init_space_error',error: e as Error,});setIsError(true);capture(new CustomError(ReportEventNames.errorPath, 'space id error', {customGlobalErrorConfig: {title: I18n.t('workspace_no_permission_access'),subtitle: (e as Error).message,},}),);}})(spaceId);}, [spaceId, isReady]);return { loading: !space.id, isError, spaceListLoading, spaceList };
};

核心功能解析

  1. 空间检查: 检查URL中的spaceId是否有效
  2. 自动跳转: 如果没有指定空间,自动跳转到个人空间或第一个可用空间
  3. 错误处理: 处理空间不存在或无权限访问的情况
  4. 状态同步: 更新全局的空间状态

useIntelligenceList Hook

文件位置:frontend/packages/studio/workspace/entry-base/src/pages/develop/hooks/use-intelligence-list.ts

import { intelligenceApi } from '@coze-arch/bot-api';import { type DraftIntelligenceList } from '../type';const getIntelligenceList = async (dataSource: DraftIntelligenceList | undefined,{spaceId,types,searchValue,hasPublished,recentlyOpen,searchScope,orderBy,}: FilterParamsType,cancelTokenRef: React.MutableRefObject<CancelTokenSource | null>,
) => {// 每次发起新请求时重置取消令牌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,};}
};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 };
};

核心功能解析

  1. 无限滚动: 使用useInfiniteScroll实现分页加载
  2. 请求取消: 使用CancelToken避免重复请求
  3. 错误处理: 统一的错误处理和用户提示
  4. 性能优化: 依赖数组控制重新请求时机
  5. 事件上报: 集成埋点上报功能
bot-api/package.json

文件位置:frontend/packages/arch/bot-api/package.json
核心代码:

{"name": "@coze-arch/bot-api","version": "0.0.1","description": "RPC wrapper for bot studio application","author": "fanwenjie.fe@bytedance.com","exports": {".": "./src/index.ts",},
}

代码作用:

  • 1.包定义 :定义了一个名为 @coze-arch/bot-api 的 npm 包,版本为 0.0.1,这是一个用于 bot studio 应用的 RPC 包装器。
  • 2.通过主入口文件 :
    frontend\packages\arch\bot-api\src\index.ts 中, patPermissionApi 被导出:
export { intelligenceApi } from './intelligence-api';

这允许通过 @coze-arch/bot-api 直接导入 intelligenceApi 。
3.intelligenceApi 实现 :在 src/intelligence-api.ts 中, intelligenceApi 是一个配置好的服务实例,它使用了 IntelligenceApiService 和 axios 请求配置。

src/pat-permission-api.ts

文件位置:frontend\packages\arch\bot-api\src\intelligence-api.ts

核心代码:

import IntelligenceApiService from './idl/intelligence_api';
import { axiosInstance, type BotAPIRequestConfig } from './axios';export const intelligenceApi = new IntelligenceApiService<BotAPIRequestConfig>({request: (params, config = {}) =>axiosInstance.request({...params,...config,headers: { ...params.headers, ...config.headers, 'Agw-Js-Conv': 'str' },}),
});
代码含义详解

这段代码是创建一个 IntelligenceApiService 实例的构造函数调用,具体含义如下:

IntelligenceApiService<BotAPIRequestConfig>({request: (params, config = {}) => axiosInstance.request({ ...params, ...config }),
})
  1. 泛型参数
  • IntelligenceApiService<BotAPIRequestConfig>:这是一个泛型类,BotAPIRequestConfig 是类型参数
  • BotAPIRequestConfig 定义了业务层的自定义 axios 配置类型,包含 __disableErrorToast 等业务特定字段
  1. 构造函数参数
    传入一个配置对象,包含 request 函数:
{request: (params, config = {}) => axiosInstance.request({ ...params, ...config })
}
  1. request 函数解析
    这是一个依赖注入的设计模式:
  • 参数说明

    • params:包含 HTTP 请求的基本参数(url、method、data、headers 等)
    • config:可选的额外配置,默认为空对象
  • 函数体

    • { ...params, ...config }:使用展开运算符合并参数
    • axiosInstance.request():调用 axios 实例的 request 方法发送 HTTP 请求
  1. 依赖注入模式
// IDL 生成的服务类不直接依赖具体的 HTTP 库
class IntelligenceApiService<T> {private request: any;constructor(options?: { request?: Function }) {this.request = options?.request || this.request;}
}
  1. 适配器模式
// 将 axiosInstance.request 适配为 IDL 服务所需的接口
request: (params, config) => axiosInstance.request({ ...params, ...config })
  1. 数据流转过程

  2. 业务调用intelligenceApi .GetDraftIntelligenceList

  3. 参数组装:IDL 生成的方法将业务参数转换为标准 HTTP 参数

  4. 请求发送:调用注入的 request 函数

  5. HTTP 请求:最终通过 axiosInstance.request 发送请求

  6. 优势

  • 解耦:IDL 生成的代码不直接依赖 axios,便于测试和替换
  • 类型安全:通过泛型确保配置类型的一致性
  • 可扩展:可以在 request 函数中添加业务逻辑(如错误处理、认证等)
  • 统一性:所有 API 调用都通过相同的 request 函数,便于统一管理
  1. 实际效果

当调用 GetDraftIntelligenceList 时:

// 1. IDL 生成的方法
GetDraftIntelligenceList(req, options) {const params = { url: '/api/...', method: 'POST', data: {...} };return this.request(params, options); // 调用注入的 request 函数
}// 2. 注入的 request 函数
(params, config) => {// params = { url: '/api/...', method: 'POST', data: {...} }// config = options (可能包含 __disableErrorToast 等)return axiosInstance.request({ ...params, ...config });
}

这种设计确保了代码的模块化、可测试性和可维护性。

axiosInstance说明

1.axiosInstance 在整个项目中是全局共享的
2.bot-api 包中的导入 ( frontend/packages/arch/bot-api/src/axios.ts )
是直接从 @coze-arch/bot-http 包导入了 axiosInstance 。

import {axiosInstance,isApiError,type AxiosRequestConfig,
} from '@coze-arch/bot-http';

3.bot-http 包中的定义 ( frontend/packages/arch/bot-http/src/axios.ts ):

export const axiosInstance = axios.create();

这里创建了一个全局的 axios 实例,与用户名修改保存请求的 axios 实例是同一个。

IntelligenceApiService说明

1.bot-api包中的导入路径:
import IntelligenceApiService from ‘./idl/intelligence_api’;
实际指向
frontend/packages/arch/bot-api/src/idl/intelligence_api.ts
文件内容重新导出了 @coze-arch/idl/intelligence_api 包的所有内容,包括默认导出

export * from '@coze-arch/idl/intelligence_api';
export { default as default } from '@coze-arch/idl/intelligence_api';

2.idl包的模块映射
文件位置:frontend/packages/arch/idl/package.json
核心代码:

"name": "@coze-arch/idl","version": "0.0.1","description": "IDL files for bot studio application","author": "fanwenjie.fe@bytedance.com","exports": {"./intelligence_api": "./src/auto-generated/intelligence_api/index.ts",

代码作用:将 @coze-arch/idl/intelligence_api 映射到实际文件路径frontend/packages/arch/idl/src/auto-generated/intelligence_api/index.ts
这个文件说明后续见 项目开发智能查询服务-API接口实现 这个章节。

API层设计与实现

IDL基础类型定义(base.thrift)

文件位置:idl/base.thrift
核心代码:

namespace py base
namespace go base
namespace java com.bytedance.thrift.basestruct TrafficEnv {1: bool   Open = false,2: string Env  = ""   ,
}struct Base {1:          string             LogID      = "",2:          string             Caller     = "",3:          string             Addr       = "",4:          string             Client     = "",5: optional TrafficEnv         TrafficEnv     ,6: optional map<string,string> Extra          ,
}struct BaseResp {1:          string             StatusMessage = "",2:          i32                StatusCode    = 0 ,3: optional map<string,string> Extra             ,
}struct EmptyReq {
}struct EmptyData {}struct EmptyResp {1: i64       code,2: string    msg ,3: EmptyData data,
}struct EmptyRpcReq {255: optional Base Base,
}struct EmptyRpcResp {255: optional BaseResp BaseResp,
}

文件作用:
定义了项目中所有接口的基础数据结构,作为其他IDL文件的依赖基础。

项目开发智能查询服务-IDL结构体定义(search.thrift)

文件路径:idl\app\search.thrift
核心代码:

namespace go app.intelligence
include "../base.thrift"
include  "common_struct/intelligence_common_struct.thrift"
include  "common_struct/common_struct.thrift"struct GetDraftIntelligenceListOption {1: bool need_replica, //need personal version Bot data
}struct GetDraftIntelligenceListRequest {1: required i64 space_id (agw.js_conv="str", api.js_conv="true"),2: optional string name,3: optional bool has_published,4: optional list<intelligence_common_struct.IntelligenceStatus> status,5: optional list<intelligence_common_struct.IntelligenceType> types,6: optional SearchScope search_scope,51: optional bool is_fav,52: optional bool recently_open,99: optional GetDraftIntelligenceListOption option,100: optional OrderBy order_by,101: optional string cursor_id,102: optional i32 size,255: optional base.Base Base
}struct IntelligencePublishInfo {1: string                      publish_time,2: bool                        has_published,3: list<common_struct.ConnectorInfo> connectors,
}struct IntelligencePermissionInfo {1: bool in_collaboration,2: bool can_delete,   // can delete3: bool can_view,     // Whether the current user can view it, the current judgment logic is whether the user is in the space where the bot is located
}struct FavoriteInfo {1: bool is_fav, // Whether to collect; use the collection list2: string fav_time, // Collection time; collection list use
}enum BotMode {SingleMode = 0MultiMode  = 1WorkflowMode = 2
}struct OtherInfo {1: string recently_open_time,   // Last opened time; used when recently opened filter2: BotMode bot_mode, // Only bot type returns
}struct Intelligence {1: intelligence_common_struct.IntelligenceBasicInfo        basic_info,     // Basic information2: intelligence_common_struct.IntelligenceType             type,           // Agent Type3: IntelligencePublishInfo      publish_info,   // Agent publishes information, optional4: common_struct.User                        owner_info,     // Agent owner information, optional5: IntelligencePermissionInfo   permission_info, // The current user's permission information to the agent, optional
}// For the front end
struct IntelligenceData {1: intelligence_common_struct.IntelligenceBasicInfo        basic_info,2: intelligence_common_struct.IntelligenceType             type,3: IntelligencePublishInfo      publish_info,4: IntelligencePermissionInfo   permission_info,5: common_struct.User           owner_info,6: common_struct.AuditInfo      latest_audit_info,7: FavoriteInfo                 favorite_info,50: OtherInfo                   other_info,
}struct DraftIntelligenceListData {1: list<IntelligenceData> intelligences,2: i32 total,3: bool has_more,4: string next_cursor_id,
}struct GetDraftIntelligenceListResponse {1: DraftIntelligenceListData data,253: i32 code,254: string msg,255: optional base.BaseResp BaseResp (api.none="true"),
}

源码作用:定义PAT权限添加令牌相关的数据结构

项目开发智能查询服务-IDL接口定义(intelligence.thrift)

文件路径:idl\app\intelligence.thrift
核心代码:

include "../base.thrift"
include "search.thrift"
include  "common_struct/intelligence_common_struct.thrift"
include  "common_struct/common_struct.thrift"namespace go app.intelligenceservice IntelligenceService {search.GetDraftIntelligenceListResponse GetDraftIntelligenceList(1: search.GetDraftIntelligenceListRequest req) (api.post='/api/intelligence_api/search/get_draft_intelligence_list', api.category="search",agw.preserve_base="true")}

源码作用:项目开发智能查询服务相关的接口

项目开发智能查询服务–结构体实现(search.ts)

文件路径:frontend\packages\arch\idl\src\auto-generated\intelligence_api\namespaces\search.ts

import * as intelligence_common_struct from './intelligence_common_struct';
import * as common_struct from './common_struct';
import * as base from './base';
import * as ocean_project_common_struct from './ocean_project_common_struct';export interface DraftIntelligenceListData {intelligences?: Array<IntelligenceData>;total?: number;has_more?: boolean;next_cursor_id?: string;
}export interface FavoriteInfo {/** 是否收藏;收藏列表使用 */is_fav?: boolean;/** 收藏时间;收藏列表使用 */fav_time?: string;
}export interface GetDraftIntelligenceListOption {/** 是否需要个人版本Bot数据 */need_replica?: boolean;
}export interface GetDraftIntelligenceListRequest {space_id: string;name?: string;has_published?: boolean;status?: Array<intelligence_common_struct.IntelligenceStatus>;types?: Array<intelligence_common_struct.IntelligenceType>;search_scope?: SearchScope;/** 文件夹id */folder_id?: string;/** 是否击穿搜索(一期不支持) */folder_include_children?: boolean;order_type?: SortOrderType;is_fav?: boolean;recently_open?: boolean;option?: GetDraftIntelligenceListOption;order_by?: OrderBy;cursor_id?: string;size?: number;Base?: base.Base;
}export interface GetDraftIntelligenceListResponse {data?: DraftIntelligenceListData;code?: number;msg?: string;
}

项目开发智能查询服务-API接口实现(intelligence_api\index.ts)

文件位置:frontend\packages\arch\idl\src\auto-generated\intelligence_api\index.ts

核心代码:

export default class IntelligenceApiService<T = any> extends BaseService<T> {constructor(options?: {baseURL?: string | ((path: string) => string);request?<R>(params: {url: string;method: 'GET' | 'DELETE' | 'POST' | 'PUT' | 'PATCH';data?: any;params?: any;headers?: any;},options?: T,): Promise<R>;}) {this.request = options?.request || this.request;this.baseURL = options?.baseURL || '';}GetDraftIntelligenceList(req: search.GetDraftIntelligenceListRequest,options?: T,): Promise<search.GetDraftIntelligenceListResponse> {const _req = req;const url = this.genBaseURL('/api/intelligence_api/search/get_draft_intelligence_list',);const method = 'POST';const data = {space_id: _req['space_id'],name: _req['name'],has_published: _req['has_published'],status: _req['status'],types: _req['types'],search_scope: _req['search_scope'],folder_id: _req['folder_id'],folder_include_children: _req['folder_include_children'],order_type: _req['order_type'],is_fav: _req['is_fav'],recently_open: _req['recently_open'],option: _req['option'],order_by: _req['order_by'],cursor_id: _req['cursor_id'],size: _req['size'],Base: _req['Base'],};return this.request({ url, method, data }, options);}// ... 其他API方法
}

代码作用:IntelligenceApiService 类有成员函数 GetDraftIntelligenceList 。
这个方法用于创建PAT权限添加令牌。
此文件是基于intelligence.thrift自动生成的,开发者无需手动修改。

IDL文件解析器分析结论

通过深入分析Coze Studio项目的IDL架构,我可以确认**intelligence.thriftpassport.thrift使用相同的Thrift Parser**。

关键发现

  1. 统一的IDL工具链:项目使用@coze-arch/idl2ts-cli作为统一的IDL到TypeScript转换工具,该工具支持处理所有Thrift文件。

  2. 共享基础结构

    • 两个文件都位于统一的coze-studio\idl目录下
    • 两个文件都引用了共享的base.thrift文件
    • 使用相同的namespace和结构体定义规范
  3. 统一的代码生成流程

    • frontend\packages\arch\api-schema\api.config.js配置了passport.thrift的生成
    • frontend\packages\arch\idl\package.json包含了intelligence.thrift的自动生成代码
    • 两者都使用相同的idl2ts工具链进行代码生成
  4. 相同的输出格式:生成的TypeScript代码都遵循相同的结构和命名约定,包含相同的注释头和类型定义格式。

结论

intelligence.thriftpassport.thrift确实使用相同的Thrift Parser(@coze-arch/idl2ts-cli),它们共享相同的解析规则、代码生成逻辑和输出格式。这确保了整个项目中IDL文件处理的一致性和兼容性。

@coze-arch/idl2ts-cli 工具详细信息

工具名称

@coze-arch/idl2ts-cli

详细地址

项目路径frontend/infra/idl/idl2ts-cli/

工具详细信息

版本:0.1.7

描述:IDL(Interface Definition Language)到TypeScript的转换工具

主要功能

  1. gen命令:从Thrift或Protocol Buffer文件生成API代码
  2. filter命令:生成过滤后的API类型定义

可执行文件idl2ts(位于 ./src/cli.js
最终调用的是frontend/infra/idl/idl2ts-cli/src/cli.ts 这个文件

核心依赖

  • @coze-arch/idl2ts-generator:代码生成器
  • @coze-arch/idl2ts-helper:辅助工具
  • @coze-arch/idl2ts-plugin:插件系统
  • commander:命令行界面
  • prettier:代码格式化

使用方式

# 生成API代码
idl2ts gen <projectRoot> [-f --format-config <formatConfig>]# 生成过滤类型
idl2ts filter <projectRoot> [-f --format-config <formatConfig>]

许可证:Apache-2.0

作者:fanwenjie.fe@bytedance.com

这个工具是Coze Studio项目中统一处理所有IDL文件(包括intelligence.thriftpassport.thrift)的核心工具,确保了整个项目中API代码生成的一致性。

状态管理机制

工作空间状态存储

文件位置:frontend/packages/foundation/space-store/src/space/index.ts

interface SpaceStoreState {/** @deprecated try useSpace instead */space: BotSpace;spaceList: BotSpace[];recentlyUsedSpaceList: BotSpace[];loading: false | Promise<SpaceInfo | undefined>;inited?: boolean;createdTeamSpaceNum: number;
}interface SpaceStoreAction {reset: () => void;/** @deprecated  get id from url */getSpaceId: () => string;getPersonalSpaceID: () => string | undefined;checkSpaceID: (spaceID: string) => boolean;/** @deprecated by id index */setSpace: (spaceId?: string, isBotDetailIframe?: boolean) => void | never;fetchSpaces: (refresh?: boolean) => Promise<SpaceInfo | undefined>;updateSpace: (space: Partial<BotSpace>) => void;
}export const useSpaceStore = create<SpaceStoreState & SpaceStoreAction>((set, get) => ({// 状态space: {} as BotSpace,spaceList: [],recentlyUsedSpaceList: [],loading: false,inited: false,createdTeamSpaceNum: 0,// 操作reset: () => {set({space: {} as BotSpace,spaceList: [],recentlyUsedSpaceList: [],loading: false,inited: false,createdTeamSpaceNum: 0,});},getSpaceId: () => {return get().space.id || '';},getPersonalSpaceID: () => {const spaceList = get().spaceList;return spaceList.find(space => space.space_type === SpaceType.Personal)?.id;},checkSpaceID: (spaceID: string) => {const spaceList = get().spaceList;return spaceList.some(space => space.id === spaceID);},setSpace: (spaceId?: string) => {if (!spaceId) return;const spaceList = get().spaceList;const targetSpace = spaceList.find(space => space.id === spaceId);if (targetSpace) {set({ space: targetSpace });// 保存到本地存储localStorageService.setValue('workspace-spaceId', spaceId);}},fetchSpaces: async (refresh = false) => {if (get().loading && !refresh) {return get().loading as Promise<SpaceInfo | undefined>;}const promise = (async () => {try {const response = await spaceApi.GetSpaceList({});const spaceList = response.data?.spaces || [];set({spaceList,inited: true,loading: false,});return response.data;} catch (error) {set({ loading: false });throw error;}})();set({ loading: promise });return promise;},updateSpace: (spaceUpdate: Partial<BotSpace>) => {const currentSpace = get().space;if (currentSpace.id === spaceUpdate.id) {set({ space: { ...currentSpace, ...spaceUpdate } });}const spaceList = get().spaceList.map(space =>space.id === spaceUpdate.id ? { ...space, ...spaceUpdate } : space);set({ spaceList });},})
);

状态管理特点

  1. 集中管理: 使用Zustand进行集中式状态管理
  2. 本地持久化: 将当前空间ID保存到localStorage
  3. 异步处理: 支持异步获取空间列表
  4. 状态同步: 提供更新和重置功能
  5. 类型安全: 完整的TypeScript类型定义

用户体验优化

1. 加载状态管理

// 骨架屏加载
const LoadingSkeleton = () => (<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">{Array.from({ length: 8 }).map((_, index) => (<div key={index} className="bg-white rounded-lg border border-gray-200 p-4"><div className="flex items-center space-x-3 mb-3"><Skeleton className="w-10 h-10 rounded-lg" /><div className="flex-1"><Skeleton className="h-4 w-3/4 mb-2" /><Skeleton className="h-3 w-1/2" /></div></div><Skeleton className="h-3 w-full mb-2" /><Skeleton className="h-3 w-2/3" /></div>))}</div>
);// 加载更多指示器
const LoadingMore = () => (<div className="flex justify-center py-4"><Spinner size="sm" /><span className="ml-2 text-sm text-gray-500">{I18n.t('loading_more')}</span></div>
);

2. 空状态处理

const EmptyState = () => (<div className="flex flex-col items-center justify-center py-12"><IconEmpty className="w-16 h-16 text-gray-300 mb-4" /><h3 className="text-lg font-medium text-gray-900 mb-2">{I18n.t('workspace_develop_empty_title')}</h3><p className="text-gray-500 text-center mb-6 max-w-md">{I18n.t('workspace_develop_empty_description')}</p><CreateButton variant="primary" /></div>
);

3. 错误处理机制

// 全局错误边界
const ErrorBoundary = ({ children }: { children: React.ReactNode }) => {return (<ReactErrorBoundaryFallbackComponent={({ error, resetErrorBoundary }) => (<div className="flex flex-col items-center justify-center py-12"><IconError className="w-16 h-16 text-red-400 mb-4" /><h3 className="text-lg font-medium text-gray-900 mb-2">{I18n.t('something_went_wrong')}</h3><p className="text-gray-500 text-center mb-6">{error.message}</p><Button onClick={resetErrorBoundary}>{I18n.t('try_again')}</Button></div>)}onError={(error, errorInfo) => {logger.error('Workspace error:', error, errorInfo);}}>{children}</ReactErrorBoundary>);
};

4. 性能优化策略

// 虚拟滚动优化(大量数据时)
const VirtualizedGrid = ({ items, renderItem }) => {const containerRef = useRef<HTMLDivElement>(null);const [visibleRange, setVisibleRange] = useState({ start: 0, end: 20 });useEffect(() => {const container = containerRef.current;if (!container) return;const handleScroll = throttle(() => {const scrollTop = container.scrollTop;const containerHeight = container.clientHeight;const itemHeight = 200; // 估算的卡片高度const itemsPerRow = Math.floor(container.clientWidth / 300); // 估算每行卡片数const start = Math.floor(scrollTop / itemHeight) * itemsPerRow;const end = start + Math.ceil(containerHeight / itemHeight) * itemsPerRow + itemsPerRow;setVisibleRange({ start: Math.max(0, start), end: Math.min(items.length, end) });}, 100);container.addEventListener('scroll', handleScroll);return () => container.removeEventListener('scroll', handleScroll);}, [items.length]);const visibleItems = items.slice(visibleRange.start, visibleRange.end);return (<div ref={containerRef} className="h-full overflow-auto"><div style={{ height: Math.ceil(items.length / 4) * 200 }}><div style={{ transform: `translateY(${Math.floor(visibleRange.start / 4) * 200}px)` }}className="grid grid-cols-4 gap-6">{visibleItems.map(renderItem)}</div></div></div>);
};// 图片懒加载
const LazyImage = ({ src, alt, className }) => {const [isLoaded, setIsLoaded] = useState(false);const [isInView, setIsInView] = useState(false);const imgRef = useRef<HTMLImageElement>(null);useEffect(() => {const observer = new IntersectionObserver(([entry]) => {if (entry.isIntersecting) {setIsInView(true);observer.disconnect();}},{ threshold: 0.1 });if (imgRef.current) {observer.observe(imgRef.current);}return () => observer.disconnect();}, []);return (<div ref={imgRef} className={className}>{isInView && (<imgsrc={src}alt={alt}className={`transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`}onLoad={() => setIsLoaded(true)}/>)}{!isLoaded && (<div className="bg-gray-200 animate-pulse w-full h-full" />)}</div>);
};

各组件之间的调用关系

路由层 (routes/index.tsx)↓ 渲染
布局层 (SpaceLayout → SpaceIdLayout)↓ 初始化
状态管理层 (useInitSpace → useSpaceStore)↓ 数据获取
页面组件层 (Develop)↓ 调用
业务逻辑层 (useIntelligenceList)↓ 请求
API层 (intelligenceApi.GetDraftIntelligenceList)↓ 渲染
展示组件层 (BotCard)

这种分层设计确保了:

  • 职责清晰:每个层级专注于特定的功能职责
  • 数据流向:单向数据流,便于调试和维护
  • 组件复用:底层组件可以在不同场景中复用
  • 状态隔离:不同层级的状态相互独立

详细调用流程

  1. 路由匹配:用户访问/space/:space_id/develop时,React Router匹配到对应路由
  2. 布局初始化:SpaceLayout组件调用useInitSpace检查和初始化工作空间
  3. 状态同步useInitSpace通过useSpaceStore管理工作空间状态
  4. 页面渲染:Develop组件根据当前空间ID渲染项目开发页面
  5. 数据获取useIntelligenceList调用API获取项目列表数据
  6. 组件展示:BotCard组件渲染每个项目的卡片信息

总结

Coze Studio的工作空间项目开发系统展现了现代前端应用的最佳实践:

  1. 模块化架构: 将工作空间功能拆分为独立的包,实现了高内聚、低耦合的设计
  2. 智能路由: 实现了自动跳转和错误处理的智能路由系统
  3. 状态管理: 使用Zustand进行高效的状态管理,支持本地持久化
  4. 数据获取: 实现了无限滚动、请求取消、错误处理的完善数据获取机制
  5. 用户体验: 提供了加载状态、空状态、错误状态的完整用户体验
  6. 性能优化: 采用虚拟滚动、图片懒加载、组件懒加载等性能优化策略
  7. 类型安全: 全面使用TypeScript,基于IDL自动生成API类型定义
  8. 响应式设计: 实现了适配不同屏幕尺寸的响应式布局
  9. 国际化支持: 完整的多语言支持系统
  10. 错误边界: 实现了完善的错误边界和错误处理机制
  11. 事件上报: 集成了完整的用户行为和错误上报系统
  12. 组件复用: 通过适配器模式实现了组件的高度复用

这套工作空间系统的设计思路和实现方式,为构建复杂的企业级前端应用提供了很好的参考价值。通过合理的架构设计和技术选型,实现了功能完整、性能优秀、用户体验良好的工作空间管理系统。整个系统从路由层到组件层都有完善的设计,体现了现代前端工程化的最佳实践,特别是在大型项目的模块化管理、状态同步、数据获取等方面提供了优秀的解决方案。

http://www.xdnf.cn/news/19338.html

相关文章:

  • 如何重置SVN被保存的用户名和密码
  • 【pve】
  • 轻量化注意力+脉冲机制,Transformer在低功耗AI中再度进化
  • 吴恩达机器学习作业十 PCA主成分分析
  • 基于单片机智能大棚/温室大棚/智慧农业/智能栽培种植系统/温湿度控制
  • LeetCode 37.解数独
  • k8s三阶段项目
  • 狂神说--Nginx--通俗易懂
  • 线程池八股文
  • 从零开始写个deer-flow-mvp-第一天
  • 拆分TypeScript项目的学习收获:处理编译缓存和包缓存,引用本地项目,使用相对路径
  • 粗糙表面接触模型MATLAB代码
  • 多租户配额与预算:限额、配额周期与突发桶的结算模型(Final)
  • 【机械故障】使用扭矩计算物体重量
  • web墨卡托的纬度范围为什么是85°S~85°N?
  • 为何重定义库函数会减少flash体积(从prinf讲解)
  • 为什么计算机使用补码存储整数:补码的本质
  • 【秋招笔试】2025.08.29阿里云秋招笔试题
  • 【Linux】动静态库的制作与原理
  • 第三十二天:数组
  • 刷算法题-数组-02
  • 关于Ctrl+a不能全选的问题
  • Wi-Fi技术——OSI模型
  • VS安装 .NETFramework,Version=v4.6.x
  • React Hooks useMemo
  • [强网杯2019]随便注-----堆叠注入,预编译
  • centos7挂载iscis存储操作记录
  • postman 用于接口测试,举例
  • postman带Token测试接口
  • DAY50打卡