feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
25
frontend/packages/arch/bot-hooks/src/global.d.ts
vendored
Normal file
25
frontend/packages/arch/bot-hooks/src/global.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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' />
|
||||
|
||||
declare const ENABLE_COVERAGE: boolean;
|
||||
interface Window {
|
||||
/**
|
||||
* tea 实例
|
||||
*/
|
||||
Tea?: any;
|
||||
}
|
||||
55
frontend/packages/arch/bot-hooks/src/index.ts
Normal file
55
frontend/packages/arch/bot-hooks/src/index.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 {
|
||||
useSetResponsiveBodyStyle,
|
||||
useIsResponsive,
|
||||
} from '@coze-arch/bot-hooks-adapter';
|
||||
export {
|
||||
useRouteConfig,
|
||||
TRouteConfigGlobal,
|
||||
useIsResponsiveByRouteConfig,
|
||||
useLoggedIn,
|
||||
useLineClamp,
|
||||
useInitialValue,
|
||||
useExposure,
|
||||
UseExposureParams,
|
||||
useComponentState,
|
||||
ComponentStateUpdateFunc,
|
||||
useDragAndPasteUpload,
|
||||
UseDragAndPasteUploadParam,
|
||||
useDefaultExPandCheck,
|
||||
useResetLocationState,
|
||||
PlacementEnum,
|
||||
useLayoutContext,
|
||||
LayoutContext,
|
||||
usePageState,
|
||||
PageStateUpdateFunc,
|
||||
useUserSenderInfo,
|
||||
useMessageReportEvent,
|
||||
} from '@coze-arch/bot-hooks-base';
|
||||
|
||||
export {
|
||||
usePageJumpService,
|
||||
usePageJumpResponse,
|
||||
SceneResponseType,
|
||||
} from './page-jump';
|
||||
export {
|
||||
SceneType,
|
||||
PageType,
|
||||
WorkflowModalState,
|
||||
OpenModeType,
|
||||
} from './page-jump/config';
|
||||
491
frontend/packages/arch/bot-hooks/src/page-jump/config.ts
Normal file
491
frontend/packages/arch/bot-hooks/src/page-jump/config.ts
Normal file
@@ -0,0 +1,491 @@
|
||||
/*
|
||||
* 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 WorkflowMode,
|
||||
type WorkFlowListStatus,
|
||||
} from '@coze-arch/bot-api/workflow_api';
|
||||
|
||||
import type { PageJumpExecFunc } from '.';
|
||||
|
||||
/**
|
||||
* workflow 弹窗打开模式,默认不传,或仅添加一次
|
||||
*/
|
||||
export enum OpenModeType {
|
||||
OnlyOnceAdd = 'only_once_add',
|
||||
}
|
||||
/**
|
||||
* 记录 workflow 弹窗的选中状态
|
||||
*/
|
||||
export interface WorkflowModalState {
|
||||
/**
|
||||
* @deprecated 是否已发布的状态
|
||||
*/
|
||||
status?: WorkFlowListStatus;
|
||||
/**
|
||||
* @deprecated 左边菜单栏选中的类型,注意这个 type 是前端翻译过的,与接口请求参数里的 type 不是同一个
|
||||
*/
|
||||
type?: number | string;
|
||||
/**
|
||||
* 弹窗状态 JSON 字符串
|
||||
*/
|
||||
statusStr?: string;
|
||||
}
|
||||
|
||||
// #region ---------------------- step 1. 声明场景枚举,若涉及新页面则也声明一下页面枚举 ----------------------
|
||||
// (添加了页面或场景枚举后,整个文件会有多处 ts 报错,这是预期内的。根据 step 指引一步一步完善配置即可)
|
||||
|
||||
/**
|
||||
* 目标页面枚举,用于聚合【场景(scene)】,便于根据页面对当前场景做 narrowing
|
||||
*
|
||||
* e.g. 从 A 页跳转到 B 页,只需要定义 B 的页面枚举
|
||||
*
|
||||
*
|
||||
* - Q: 为什么不使用默认的自增 enum 数值,每个页面手写两遍名字好麻烦
|
||||
* - A: 便于调试时能直接看出含义,不用查代码对照,下同
|
||||
*/
|
||||
export const enum PageType {
|
||||
BOT = 'bot',
|
||||
WORKFLOW = 'workflow',
|
||||
PLUGIN_MOCK_DATA = 'plugin_mock_data',
|
||||
KNOWLEDGE = 'knowledge',
|
||||
SOCIAL_SCENE = 'social_scene',
|
||||
DOUYIN_BOT = 'douyin_bot',
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention -- 有必要 disable,该场景需要不同的 enum 命名规范 */
|
||||
/**
|
||||
* 每种跳转场景的唯一枚举
|
||||
*
|
||||
* e.g. 比如 bot 编辑页创建 workflow 是一种场景,查看 workflow 是一种场景
|
||||
*
|
||||
* 枚举定义规范:
|
||||
* 1. 最常见的场景:两个页面简单跳转可以按 `{源页面}__TO__{目标页面}` 的格式命名。
|
||||
* (注意 TO 前后各有两个下划线,以便区分页面名为多个单词的场景,比如 BOT_LIST__TO__BOT_DETAIL,后文不再赘述)
|
||||
* 2. 从源页面可能存在多种跳转到目标页面的场景,则可以按 `{源页面}__{行为}__{目标页面}` 格式命名。
|
||||
* 3. 如果目标页面逻辑很简单,又有多处跳转来源,则可以按 `TO__{目标页面}` 格式命名。
|
||||
* 4. 对于「跳转到目标页再返回」的特化逻辑,可以给已有的场景命名添加 `__{后缀}`,比如 BOT__CREATE__WORKFLOW__BACK
|
||||
*
|
||||
* - Q: 我觉得一个目标页面无脑使用 `TO__{目标页面}` 的格式就没问题啊,业务逻辑、来源判断什么我都可以在 参数(param) 和 响应值(response) 中完成
|
||||
* - A: 的确,一个目标页面只声明一种场景就可以打遍天下,这里只是提供了逐级细化拆分场景的范式。
|
||||
* 从 `TO__{目标页面}` 到 `{源页面}__TO__{目标页面}` 再到 `{源页面}__{行为}__{目标页面}` 场景是越来越细分的,业务方可以自行决定如何使用
|
||||
*/
|
||||
export const enum SceneType {
|
||||
/** bot 详情页查看 workflow */
|
||||
BOT__VIEW__WORKFLOW = 'botViewWorkflow',
|
||||
/** bot 详情页查看 workflow,或新建 workflow 但未发布,点击返回 */
|
||||
WORKFLOW__BACK__BOT = 'workflowBackBot',
|
||||
/** bot 详情页创建 workflow,在 workflow 发布后返回 */
|
||||
WORKFLOW_PUBLISHED__BACK__BOT = 'workflowPublishedBackBot',
|
||||
|
||||
/** 抖音 bot 详情查看 workflow */
|
||||
DOUYIN_BOT__VIEW__WORKFLOW = 'douyinBotViewWorkflow',
|
||||
|
||||
/** 抖音 bot 详情页返回 */
|
||||
WORKFLOW__BACK__DOUYIN_BOT = 'workflowBackDouyinBot',
|
||||
|
||||
/** 抖音 bot 详情页发布后返回 */
|
||||
WORKFLOW_PUBLISHED__BACK__DOUYIN_BOT = 'workflowPulishedBackDouyinBot',
|
||||
|
||||
/** bot 详情页进入 mock data 页面 */
|
||||
BOT__TO__PLUGIN_MOCK_DATA = 'botToPluginMockData',
|
||||
/** workflow 详情页进入 mock data 页面 */
|
||||
WORKFLOW__TO__PLUGIN_MOCK_DATA = 'workflowToPluginMockData',
|
||||
/** mock set 页进入 mock data 页面 */
|
||||
PLUGIN_MOCK_SET__TO__PLUGIN_MOCK_DATA = 'pluginMockSetToPluginMockData',
|
||||
/** bot 详情页进入 knowledge 页面 */
|
||||
BOT__VIEW__KNOWLEDGE = 'botViewKnowledge',
|
||||
/** knowledge 页面点击退出返回 bot 详情页(未点击添加) */
|
||||
KNOWLEDGE__BACK__BOT = 'knowledgeBackBot',
|
||||
/** knowledge 页面点击返回 bot 详情页,并添加到 bot */
|
||||
KNOWLEDGE__ADD_TO__BOT = 'knowledgeAddToBot',
|
||||
/** bot 列表页进入bot 详情页,并查看发布结果 */
|
||||
BOT_LIST__VIEW_PUBLISH_RESULT_IN__BOT_DETAIL = 'botListViewPublishResultInBotDetail',
|
||||
/** bot 列表页进入bot 详情页,并查看发布结果 */
|
||||
BOT_LIST__VIEW_PUBLISH_RESULT_IN__DOUYIN_DETAIL = 'botListViewPublishResultInDouyinDetail',
|
||||
/** social scene 页面查看 workflow */
|
||||
SOCIAL_SCENE__VIEW__WORKFLOW = 'socialSceneViewWorkflow',
|
||||
/** social scene 详情页查看 workflow,或新建 workflow 但未发布,点击返回 */
|
||||
WORKFLOW__BACK__SOCIAL_SCENE = 'workflowBackSocialScene',
|
||||
/** social scene 详情页创建或查看 workflow,在 workflow 发布后返回 */
|
||||
WORKFLOW_PUBLISHED__BACK__SOCIAL_SCENE = 'workflowPublishedBackSocialScene',
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/naming-convention -- 恢复 enum 命名规范 */
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region ---------------------- step 2. 将声明的场景枚举绑定至页面 ----------------------
|
||||
|
||||
/** 绑定某一页面可能包含的场景 */
|
||||
export const PAGE_SCENE_MAP = {
|
||||
[PageType.WORKFLOW]: [
|
||||
SceneType.BOT__VIEW__WORKFLOW,
|
||||
SceneType.SOCIAL_SCENE__VIEW__WORKFLOW,
|
||||
SceneType.DOUYIN_BOT__VIEW__WORKFLOW,
|
||||
],
|
||||
[PageType.BOT]: [
|
||||
SceneType.WORKFLOW__BACK__BOT,
|
||||
SceneType.WORKFLOW_PUBLISHED__BACK__BOT,
|
||||
SceneType.KNOWLEDGE__BACK__BOT,
|
||||
SceneType.KNOWLEDGE__ADD_TO__BOT,
|
||||
SceneType.BOT_LIST__VIEW_PUBLISH_RESULT_IN__BOT_DETAIL,
|
||||
],
|
||||
[PageType.DOUYIN_BOT]: [
|
||||
SceneType.WORKFLOW__BACK__DOUYIN_BOT,
|
||||
SceneType.WORKFLOW_PUBLISHED__BACK__DOUYIN_BOT,
|
||||
SceneType.BOT_LIST__VIEW_PUBLISH_RESULT_IN__DOUYIN_DETAIL,
|
||||
],
|
||||
[PageType.PLUGIN_MOCK_DATA]: [
|
||||
SceneType.BOT__TO__PLUGIN_MOCK_DATA,
|
||||
SceneType.WORKFLOW__TO__PLUGIN_MOCK_DATA,
|
||||
SceneType.PLUGIN_MOCK_SET__TO__PLUGIN_MOCK_DATA,
|
||||
],
|
||||
[PageType.KNOWLEDGE]: [SceneType.BOT__VIEW__KNOWLEDGE],
|
||||
[PageType.SOCIAL_SCENE]: [
|
||||
SceneType.WORKFLOW__BACK__SOCIAL_SCENE,
|
||||
SceneType.WORKFLOW_PUBLISHED__BACK__SOCIAL_SCENE,
|
||||
],
|
||||
} satisfies Record<PageType, SceneType[]>;
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region ---------------------- step 3. 声明新场景的参数类型 ----------------------
|
||||
// 【参数(param)】表示从 A 页面跳转 B 页面时,需要 A 页面填写的数据。这份数据会作为 route state 传递
|
||||
// (B 页面会取得处理后的数据,称为响应值)
|
||||
|
||||
interface BotViewWorkflow {
|
||||
spaceID: string;
|
||||
workflowID: string;
|
||||
botID?: string;
|
||||
workflowModalState?: WorkflowModalState;
|
||||
/** multi 模式下会有此项 */
|
||||
agentID?: string;
|
||||
/** @deprecated workflow弹窗打开模式,默认和仅可添加一次 */
|
||||
workflowOpenMode?: OpenModeType;
|
||||
flowMode?: WorkflowMode;
|
||||
/** 是否在新窗口打开 */
|
||||
newWindow?: boolean;
|
||||
/** 可选的工作流节点 ID */
|
||||
workflowNodeID?: string;
|
||||
/** 可选的工作流版本 */
|
||||
workflowVersion?: string;
|
||||
/** 可选的执行 ID */
|
||||
executeID?: string;
|
||||
/** 可选的子流程执行 ID */
|
||||
subExecuteID?: string;
|
||||
}
|
||||
|
||||
interface WorkflowBackBot {
|
||||
spaceID: string;
|
||||
botID: string;
|
||||
workflowModalState?: WorkflowModalState;
|
||||
/** multi 模式下会有此项 */
|
||||
agentID?: string;
|
||||
/** workflow弹窗打开模式,默认和仅可添加一次 */
|
||||
workflowOpenMode?: OpenModeType;
|
||||
flowMode?: WorkflowMode;
|
||||
}
|
||||
|
||||
interface WorkflowPulishedBackBot {
|
||||
spaceID: string;
|
||||
botID: string;
|
||||
workflowID: string;
|
||||
pluginID: string;
|
||||
/** multi 模式下会有此项 */
|
||||
agentID?: string;
|
||||
/** workflow弹窗打开模式,默认和仅可添加一次 */
|
||||
workflowOpenMode?: OpenModeType;
|
||||
flowMode?: WorkflowMode;
|
||||
}
|
||||
|
||||
/** 绑定场景的参数类型 */
|
||||
export type SceneParamTypeMap<T extends SceneType> = {
|
||||
[SceneType.BOT__VIEW__WORKFLOW]: BotViewWorkflow;
|
||||
[SceneType.DOUYIN_BOT__VIEW__WORKFLOW]: BotViewWorkflow;
|
||||
[SceneType.WORKFLOW__BACK__BOT]: WorkflowBackBot;
|
||||
[SceneType.WORKFLOW__BACK__DOUYIN_BOT]: WorkflowBackBot;
|
||||
[SceneType.WORKFLOW_PUBLISHED__BACK__BOT]: WorkflowPulishedBackBot;
|
||||
[SceneType.WORKFLOW_PUBLISHED__BACK__DOUYIN_BOT]: WorkflowPulishedBackBot;
|
||||
[SceneType.BOT__TO__PLUGIN_MOCK_DATA]: {
|
||||
spaceId: string;
|
||||
pluginId: string;
|
||||
pluginName?: string;
|
||||
toolId: string;
|
||||
toolName?: string;
|
||||
mockSetId: string;
|
||||
mockSetName?: string;
|
||||
generationMode?: number;
|
||||
bizCtx: string;
|
||||
bindSubjectInfo: string;
|
||||
};
|
||||
[SceneType.WORKFLOW__TO__PLUGIN_MOCK_DATA]: {
|
||||
spaceId: string;
|
||||
pluginId: string;
|
||||
pluginName?: string;
|
||||
toolId: string;
|
||||
toolName?: string;
|
||||
mockSetId: string;
|
||||
mockSetName?: string;
|
||||
generationMode?: number;
|
||||
bizCtx: string;
|
||||
bindSubjectInfo: string;
|
||||
};
|
||||
[SceneType.PLUGIN_MOCK_SET__TO__PLUGIN_MOCK_DATA]: {
|
||||
spaceId: string;
|
||||
pluginId: string;
|
||||
pluginName?: string;
|
||||
toolId: string;
|
||||
toolName?: string;
|
||||
mockSetId: string;
|
||||
mockSetName?: string;
|
||||
generationMode?: number;
|
||||
bizCtx?: string;
|
||||
bindSubjectInfo?: string;
|
||||
};
|
||||
[SceneType.BOT__VIEW__KNOWLEDGE]: {
|
||||
spaceID?: string;
|
||||
botID?: string;
|
||||
knowledgeID?: string;
|
||||
};
|
||||
[SceneType.KNOWLEDGE__BACK__BOT]: {
|
||||
spaceID?: string;
|
||||
botID?: string;
|
||||
mode: 'bot' | 'douyin';
|
||||
};
|
||||
[SceneType.KNOWLEDGE__ADD_TO__BOT]: {
|
||||
spaceID?: string;
|
||||
botID?: string;
|
||||
knowledgeID?: string;
|
||||
};
|
||||
[SceneType.BOT_LIST__VIEW_PUBLISH_RESULT_IN__BOT_DETAIL]: {
|
||||
publishId: string;
|
||||
commitVersion: string;
|
||||
spaceId: string;
|
||||
botId: string;
|
||||
};
|
||||
[SceneType.BOT_LIST__VIEW_PUBLISH_RESULT_IN__DOUYIN_DETAIL]: {
|
||||
publishId: string;
|
||||
commitVersion: string;
|
||||
spaceId: string;
|
||||
botId: string;
|
||||
};
|
||||
[SceneType.SOCIAL_SCENE__VIEW__WORKFLOW]: {
|
||||
spaceID: string;
|
||||
workflowID: string;
|
||||
sceneID: string;
|
||||
workflowModalState?: WorkflowModalState;
|
||||
flowMode?: WorkflowMode;
|
||||
/** 是否在新窗口打开 */
|
||||
newWindow?: boolean;
|
||||
};
|
||||
[SceneType.WORKFLOW__BACK__SOCIAL_SCENE]: {
|
||||
spaceID: string;
|
||||
sceneID: string;
|
||||
workflowModalState?: WorkflowModalState;
|
||||
flowMode?: WorkflowMode;
|
||||
};
|
||||
[SceneType.WORKFLOW_PUBLISHED__BACK__SOCIAL_SCENE]: {
|
||||
spaceID: string;
|
||||
workflowID: string;
|
||||
sceneID: string;
|
||||
pluginID: string;
|
||||
flowMode?: WorkflowMode;
|
||||
};
|
||||
}[T];
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region ---------------------- step 4. 配置新场景的响应值 ----------------------
|
||||
// 【响应值(response)】表示从 A 页面跳转 B 页面时,B 页面获取的数据
|
||||
// Q: 为什么 B 页面不能直接拿到 参数(param),还得转换一下?
|
||||
// A: 1. route state 无法传递 不能 stringify 的参数,比如函数;
|
||||
// 2. 静态配置(response)和动态配置(param)分离,简化业务调用。
|
||||
|
||||
// 若未来这部分配置不断膨胀导致文件过长,则可以考虑进一步拆分文件
|
||||
|
||||
/** 绑定场景的响应值 */
|
||||
export const SCENE_RESPONSE_MAP = {
|
||||
// 临时修正类型推导问题,待有场景需要第二个参数 jump 时删掉此处 _
|
||||
[SceneType.BOT__VIEW__WORKFLOW]: (params, _) => {
|
||||
let url = `/work_flow?space_id=${params.spaceID}&workflow_id=${params.workflowID}`;
|
||||
|
||||
const queries = [
|
||||
['bot_id', params.botID],
|
||||
['node_id', params.workflowNodeID],
|
||||
['version', params.workflowVersion],
|
||||
['execute_id', params.executeID],
|
||||
['sub_execute_id', params.subExecuteID],
|
||||
];
|
||||
|
||||
queries.forEach(([key, value]) => {
|
||||
if (value && value.length > 0) {
|
||||
url += `&${key}=${value}`;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
url,
|
||||
botID: params.botID,
|
||||
workflowModalState: params.workflowModalState,
|
||||
agentID: params.agentID,
|
||||
workflowOpenMode: params.workflowOpenMode,
|
||||
flowMode: params.flowMode,
|
||||
};
|
||||
},
|
||||
[SceneType.DOUYIN_BOT__VIEW__WORKFLOW]: (params, _) => {
|
||||
let url = `/work_flow?space_id=${params.spaceID}&workflow_id=${params.workflowID}`;
|
||||
|
||||
const queries = [
|
||||
['bot_id', params.botID],
|
||||
['node_id', params.workflowNodeID],
|
||||
['version', params.workflowVersion],
|
||||
['execute_id', params.executeID],
|
||||
['sub_execute_id', params.subExecuteID],
|
||||
];
|
||||
|
||||
queries.forEach(([key, value]) => {
|
||||
if (value && value.length > 0) {
|
||||
url += `&${key}=${value}`;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
url,
|
||||
botID: params.botID,
|
||||
workflowModalState: params.workflowModalState,
|
||||
agentID: params.agentID,
|
||||
workflowOpenMode: params.workflowOpenMode,
|
||||
flowMode: params.flowMode,
|
||||
};
|
||||
},
|
||||
[SceneType.WORKFLOW__BACK__BOT]: params => ({
|
||||
url: `/space/${params.spaceID}/bot/${params.botID}`,
|
||||
workflowModalState: params.workflowModalState,
|
||||
agentID: params.agentID,
|
||||
workflowOpenMode: params.workflowOpenMode,
|
||||
flowMode: params.flowMode,
|
||||
}),
|
||||
[SceneType.WORKFLOW__BACK__DOUYIN_BOT]: params => ({
|
||||
url: `/space/${params.spaceID}/douyin-bot/${params.botID}`,
|
||||
workflowModalState: params.workflowModalState,
|
||||
agentID: params.agentID,
|
||||
workflowOpenMode: params.workflowOpenMode,
|
||||
flowMode: params.flowMode,
|
||||
}),
|
||||
[SceneType.WORKFLOW_PUBLISHED__BACK__BOT]: params => ({
|
||||
url: `/space/${params.spaceID}/bot/${params.botID}`,
|
||||
workflowID: params.workflowID,
|
||||
pluginID: params.pluginID,
|
||||
agentID: params.agentID,
|
||||
workflowOpenMode: params.workflowOpenMode,
|
||||
flowMode: params.flowMode,
|
||||
}),
|
||||
[SceneType.WORKFLOW_PUBLISHED__BACK__DOUYIN_BOT]: params => ({
|
||||
url: `/space/${params.spaceID}/douyin-bot/${params.botID}`,
|
||||
workflowID: params.workflowID,
|
||||
pluginID: params.pluginID,
|
||||
agentID: params.agentID,
|
||||
workflowOpenMode: params.workflowOpenMode,
|
||||
flowMode: params.flowMode,
|
||||
}),
|
||||
[SceneType.BOT__TO__PLUGIN_MOCK_DATA]: params => {
|
||||
const { spaceId, pluginId, toolId, mockSetId } = params;
|
||||
return {
|
||||
url: `/space/${spaceId}/plugin/${pluginId}/tool/${toolId}/plugin-mock-set/${mockSetId}?hideMenu=true`,
|
||||
fromSource: 'bot',
|
||||
...params,
|
||||
};
|
||||
},
|
||||
[SceneType.WORKFLOW__TO__PLUGIN_MOCK_DATA]: params => {
|
||||
const { spaceId, pluginId, toolId, mockSetId } = params;
|
||||
return {
|
||||
url: `/space/${spaceId}/plugin/${pluginId}/tool/${toolId}/plugin-mock-set/${mockSetId}?hideMenu=true&workflowPluginMockset=true`,
|
||||
fromSource: 'workflow',
|
||||
...params,
|
||||
};
|
||||
},
|
||||
[SceneType.PLUGIN_MOCK_SET__TO__PLUGIN_MOCK_DATA]: params => {
|
||||
const { spaceId, pluginId, toolId, mockSetId } = params;
|
||||
return {
|
||||
url: `/space/${spaceId}/plugin/${pluginId}/tool/${toolId}/plugin-mock-set/${mockSetId}`,
|
||||
fromSource: 'mock_set',
|
||||
back: undefined,
|
||||
...params,
|
||||
};
|
||||
},
|
||||
[SceneType.BOT__VIEW__KNOWLEDGE]: params => ({
|
||||
url: `/space/${params.spaceID}/knowledge/${params.knowledgeID}?page_mode=modal&from=bot&bot_id=${params.botID}`,
|
||||
botID: params.botID,
|
||||
}),
|
||||
[SceneType.KNOWLEDGE__BACK__BOT]: params => ({
|
||||
url: `/space/${params.spaceID}/${params.mode === 'bot' ? 'bot' : 'douyin-bot'}/${params.botID}`,
|
||||
}),
|
||||
[SceneType.KNOWLEDGE__ADD_TO__BOT]: params => ({
|
||||
url: `/space/${params.spaceID}/bot/${params.botID}`,
|
||||
knowledgeID: params.knowledgeID,
|
||||
}),
|
||||
[SceneType.BOT_LIST__VIEW_PUBLISH_RESULT_IN__BOT_DETAIL]: params => ({
|
||||
url: `/space/${params.spaceId}/bot/${params.botId}`,
|
||||
publishId: params.publishId,
|
||||
commitVersion: params.commitVersion,
|
||||
}),
|
||||
[SceneType.BOT_LIST__VIEW_PUBLISH_RESULT_IN__DOUYIN_DETAIL]: params => ({
|
||||
url: `/space/${params.spaceId}/douyin-bot/${params.botId}`,
|
||||
publishId: params.publishId,
|
||||
commitVersion: params.commitVersion,
|
||||
}),
|
||||
[SceneType.SOCIAL_SCENE__VIEW__WORKFLOW]: (params, _) => ({
|
||||
url: `/work_flow?scene_id=${params.sceneID}&space_id=${params.spaceID}&workflow_id=${params.workflowID}`,
|
||||
sceneID: params.sceneID,
|
||||
workflowModalState: params.workflowModalState,
|
||||
flowMode: params.flowMode,
|
||||
}),
|
||||
[SceneType.WORKFLOW_PUBLISHED__BACK__SOCIAL_SCENE]: (params, _) => ({
|
||||
url: `/space/${params.spaceID}/social-scene/${params.sceneID}`,
|
||||
workflowID: params.workflowID,
|
||||
pluginID: params.pluginID,
|
||||
flowMode: params.flowMode,
|
||||
}),
|
||||
[SceneType.WORKFLOW__BACK__SOCIAL_SCENE]: params => ({
|
||||
url: `/space/${params.spaceID}/social-scene/${params.sceneID}`,
|
||||
workflowModalState: params.workflowModalState,
|
||||
flowMode: params.flowMode,
|
||||
}),
|
||||
} satisfies SceneResponseConstraint;
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region ---------------------- 业务方无需关注的部分 ----------------------
|
||||
|
||||
/**
|
||||
* 辅助类型
|
||||
*
|
||||
* 该类型实现以下几件事:
|
||||
* 1. 检查 SceneType 是否被遍历,有遗漏则会报错
|
||||
* 2. 为回调方法注入参数类型
|
||||
* 3. 约束响应值必须包含某些字段(比如 url),否则报错
|
||||
* 4. 正确推导响应值的具体类型,便于后续使用
|
||||
*/
|
||||
type SceneResponseConstraint = {
|
||||
[K in SceneType]: (
|
||||
param: SceneParamTypeMap<K>,
|
||||
jump: PageJumpExecFunc,
|
||||
) => {
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
|
||||
// #endregion
|
||||
161
frontend/packages/arch/bot-hooks/src/page-jump/index.ts
Normal file
161
frontend/packages/arch/bot-hooks/src/page-jump/index.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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 { isNil } from 'lodash-es';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
PAGE_SCENE_MAP,
|
||||
type PageType,
|
||||
SCENE_RESPONSE_MAP,
|
||||
type SceneType,
|
||||
type SceneParamTypeMap,
|
||||
} from './config';
|
||||
|
||||
export { PageType, SceneType };
|
||||
|
||||
/**
|
||||
* 页面跳转 hook
|
||||
*
|
||||
* @example
|
||||
* const pageJump = usePageJump();
|
||||
*
|
||||
* pageJump.jump(SceneType.BOT_CREATE_WORKFLOW, { ...param })
|
||||
*/
|
||||
export function usePageJumpService(): {
|
||||
jump: PageJumpExecFunc;
|
||||
} {
|
||||
const navigate = useNavigate();
|
||||
return {
|
||||
jump: <T extends SceneType>(sceneType: T, param?: SceneParamTypeMap<T>) => {
|
||||
// eslint-disable-next-line max-len -- eslint 注释格式限制,不得不超出 max-len
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-function -- 1.内部类型难以推导,不影响外侧类型约束和推导 2.只是获取 url,不会使用第二个参数,空函数仅用于解决类型错误,不影响使用,更不影响调用侧类型约束和推导
|
||||
const { url } = SCENE_RESPONSE_MAP[sceneType](param as any, () => {});
|
||||
|
||||
if (!url) {
|
||||
return console.error('page jump error: no url provided');
|
||||
}
|
||||
|
||||
if (
|
||||
(param as SceneParamTypeMap<SceneType.BOT__VIEW__WORKFLOW>)?.newWindow
|
||||
) {
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
navigate(url, { state: { ...param, scene: sceneType } });
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前页面的响应值
|
||||
*
|
||||
* 如果当前页面可能有多种场景,那么返回值将是这些场景响应值的 union,需要在业务代码中根据 `scene` 来做 type narrowing
|
||||
*
|
||||
* 当没接收到场景值 或接收到的场景值与页面不匹配时,返回 null
|
||||
*
|
||||
* 注意:即使页面刷新后也会保留该响应值,若不希望刷新后也保留,需要调用 clearScene() 方法
|
||||
*
|
||||
* @example
|
||||
* const routeResponse = usePageResponse(PageType.WORKFLOW);
|
||||
* // 此时只知道是 workflow 页面,但场景可能是 查看或创建
|
||||
* if (routeResponse.scene === SceneType.BOT_CREATE_WORKFLOW) {
|
||||
* // 此时 routeResponse 能被推导为 BOT_CREATE_WORKFLOW 场景的响应值
|
||||
* }
|
||||
*/
|
||||
export function usePageJumpResponse<P extends PageType>(
|
||||
pageType: P,
|
||||
): SceneResponseType<PageSceneUnion<P>> | null {
|
||||
const { jump } = usePageJumpService();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const validScenes = PAGE_SCENE_MAP[pageType];
|
||||
const param: SceneParamTypeMap<(typeof validScenes)[number]> & {
|
||||
scene: (typeof validScenes)[number];
|
||||
} = location.state;
|
||||
|
||||
if (isNil(param?.scene)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(validScenes as SceneType[]).includes(param?.scene)) {
|
||||
// route state 传来的场景枚举值,并不存在于调用方声明的页面中
|
||||
console.error(
|
||||
"got wrong route state: this page doesn't have the scene passed by route param",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 内部类型难以推导,不影响调用侧类型推导
|
||||
...SCENE_RESPONSE_MAP[param.scene](param as any, jump),
|
||||
scene: param.scene,
|
||||
clearScene: (forceRerender = false) => {
|
||||
if (forceRerender) {
|
||||
// 清除 history.state 之后 rerender,useLocation 依然能取到清除前的值,应该是 react-router-dom 做了缓存
|
||||
// 搜索发现做一次 replace navigate 可解,且测试发现并不会导致组件重新挂载,只会 rerender
|
||||
navigate(location.pathname, { replace: true });
|
||||
return;
|
||||
}
|
||||
history.replaceState({}, '');
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 内部类型难以推导,不影响调用侧类型推导
|
||||
} as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* usePageJumpResponse().jump 的类型
|
||||
*
|
||||
* 因为要复用,所以单独声明一下
|
||||
*/
|
||||
export interface PageJumpExecFunc {
|
||||
/**
|
||||
* @param sceneType 场景
|
||||
* @param param 用户输入场景后,能推导出对应的 param 类型作为约束,若该场景无参数,则可不传 param
|
||||
*/
|
||||
<T extends SceneWithNoParam>(sceneType: T): void;
|
||||
// eslint-disable-next-line @typescript-eslint/unified-signatures -- 报错有问题,不应该合并声明
|
||||
<T extends SceneType>(sceneType: T, param: SceneParamTypeMap<T>): void;
|
||||
}
|
||||
|
||||
/** 返回 P 页面下可能的场景 */
|
||||
type PageSceneUnion<P extends PageType> = (typeof PAGE_SCENE_MAP)[P][number];
|
||||
/**
|
||||
* 获取场景的响应值类型
|
||||
*
|
||||
* 利用 distributive condition 特性,将返回的类型拆分为 union
|
||||
* 以便业务中利用 discriminated union 特性通过判断 scene 来实现 type narrowing
|
||||
*/
|
||||
export type SceneResponseType<T extends SceneType> = T extends SceneType
|
||||
? Omit<ReturnType<(typeof SCENE_RESPONSE_MAP)[T]>, 'url'> & {
|
||||
scene: T;
|
||||
/**
|
||||
* 清除当前页面绑定的一切跳转数据
|
||||
* @param forceRefresh 是否即时清空。
|
||||
* 默认需要刷新才能清空(由于 react-router-dom 的原因,即使 rerender 也会获取到清空前的响应值);
|
||||
* 传 true 时会调用一次 replace navigate,触发 rerender 并且不会再获取到响应值(不会触发组件 unmount)
|
||||
*/
|
||||
clearScene: (forceRefresh?: boolean) => void;
|
||||
}
|
||||
: never;
|
||||
/** 筛选出没有参数的场景 */
|
||||
type SceneWithNoParam = SceneType extends infer P
|
||||
? P extends SceneType
|
||||
? SceneParamTypeMap<P> extends undefined
|
||||
? P
|
||||
: never
|
||||
: never
|
||||
: never;
|
||||
Reference in New Issue
Block a user