Files
coze-studio/frontend/packages/arch/bot-hooks/src/page-jump/config.ts
fanlv 890153324f feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
2025-07-20 17:36:12 +08:00

492 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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