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,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.
*/
import { devtools, subscribeWithSelector } from 'zustand/middleware';
import { create } from 'zustand';
import { produce } from 'immer';
import { type AuditInfo } from '@coze-arch/idl/playground_api';
import { type GetDraftBotInfoAgwData } from '@coze-arch/bot-api/playground_api';
import {
type SetterAction,
setterActionFactory,
} from '../utils/setter-factory';
export const getDefaultAuditInfoStore = (): AuditInfoStore => ({
audit_status: 1,
});
export type AuditInfoStore = AuditInfo;
export interface AuditInfoAction {
setAuditInfo: SetterAction<AuditInfoStore>;
setAuditInfoByImmer: (update: (state: AuditInfoStore) => void) => void;
initStore: (botData: GetDraftBotInfoAgwData) => void;
clear: () => void;
}
export const useAuditInfoStore = create<AuditInfoStore & AuditInfoAction>()(
devtools(
subscribeWithSelector((set, get) => ({
...getDefaultAuditInfoStore(),
setAuditInfo: setterActionFactory<AuditInfoStore>(set),
setAuditInfoByImmer: update =>
set(produce<AuditInfoStore>(auditInfo => update(auditInfo))),
initStore: botData => {
const { setAuditInfo } = get();
botData && setAuditInfo(botData?.latest_audit_info ?? {});
},
clear: () => {
set({ ...getDefaultAuditInfoStore() });
},
})),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botDetail.auditInfo',
},
),
);

View File

@@ -0,0 +1,160 @@
/*
* 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 { devtools, subscribeWithSelector } from 'zustand/middleware';
import { create } from 'zustand';
import { produce } from 'immer';
import {
type BotInfo,
type GetDraftBotInfoAgwData,
type UserInfo,
type BusinessType,
} from '@coze-arch/idl/playground_api';
import {
BotMarketStatus,
BotMode,
type ConnectorInfo,
} from '@coze-arch/bot-api/developer_api';
import {
type SetterAction,
setterActionFactory,
} from '../utils/setter-factory';
export const getDefaultBotInfoStore = (): BotInfoStore => ({
botId: '',
mode: BotMode.SingleMode,
botMarketStatus: BotMarketStatus.Offline,
name: '',
description: '',
icon_uri: '',
icon_url: '',
create_time: '',
creator_id: '',
update_time: '',
connector_id: '',
publisher: {},
has_publish: false,
connectors: [],
publish_time: '',
space_id: '',
version: '',
raw: {},
});
/** 定义bot的基础信息*/
export interface BotInfoStore {
botId: string;
/** 发布的业务线详情 */
connectors: Array<ConnectorInfo>;
/** for前端发布时间 */
publish_time: string;
/** 空间id */
space_id: string;
/** 是否已发布 */
has_publish: boolean;
mode: BotMode;
/** 最新发布版本时传发布人 */
publisher: UserInfo;
/** bot上架后的商品状态 */
botMarketStatus: BotMarketStatus;
/** bot名称 */
name: string;
/** bot描述 */
description: string;
/** bot 图标uri */
icon_uri: string;
/** bot 图标url */
icon_url: string;
/** 创建时间 */
create_time: string;
/** 创建人id */
creator_id: string;
/** 更新时间 */
update_time: string;
/** 业务线 */
connector_id: string;
/** multi agent mode agent信息 */
// agents?: Array<Agent>;
/** 版本,毫秒 */
version: string;
/** multi_agent结构体 */
// multi_agent_info?: MultiAgentInfo;
/** @ 保存了原始bot数据, readonly **/
raw: BotInfo;
/** 抖音分身应用id */
appId?: string;
/** 业务类型 默认0 分身业务1 */
businessType?: BusinessType;
}
export interface BotInfoAction {
setBotInfo: SetterAction<BotInfoStore>;
setBotInfoByImmer: (update: (state: BotInfoStore) => void) => void;
transformVo2Dto: (data: GetDraftBotInfoAgwData) => BotInfoStore;
initStore: (data: GetDraftBotInfoAgwData) => void;
clear: () => void;
}
export const useBotInfoStore = create<BotInfoStore & BotInfoAction>()(
devtools(
subscribeWithSelector((set, get) => ({
...getDefaultBotInfoStore(),
setBotInfo: setterActionFactory<BotInfoStore>(set),
setBotInfoByImmer: update =>
set(produce<BotInfoStore>(state => update(state))),
// eslint-disable-next-line complexity
transformVo2Dto: data => {
// 将botData转化为botInfoStore, 只取BotInfoStore中的固定字段
const botInfo = data.bot_info ?? {};
return {
botId: botInfo?.bot_id ?? '',
mode: botInfo?.bot_mode ?? BotMode.SingleMode,
botMarketStatus: data.bot_market_status ?? BotMarketStatus.Offline,
name: botInfo.name ?? '',
description: botInfo.description ?? '',
icon_uri: botInfo.icon_uri ?? '',
icon_url: botInfo.icon_url ?? '',
create_time: botInfo.create_time ?? '',
creator_id: botInfo.creator_id ?? '',
update_time: botInfo.update_time ?? '',
connector_id: botInfo.connector_id ?? '',
version: botInfo.version ?? '',
publisher: data.publisher ?? {},
has_publish: data.has_publish ?? false,
connectors: data.connectors ?? [],
publish_time: data.publish_time ?? '',
space_id: data.space_id ?? '',
businessType: botInfo.business_type,
appId: data.app_id ?? '',
raw: botInfo,
};
},
initStore: data => {
const { transformVo2Dto } = get();
const transformedData = transformVo2Dto(data);
set(transformedData);
},
clear: () => {
set({ ...getDefaultBotInfoStore() });
},
})),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botDetail.botInfo',
},
),
);

View File

@@ -0,0 +1,99 @@
/*
* 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 { BotTableRWMode } from '@coze-arch/bot-api/memory';
import {
type BackgroundImageInfo,
SuggestedQuestionsShowMode,
SuggestReplyMode,
} from '@coze-arch/bot-api/developer_api';
import {
type VoicesInfo,
type BotSuggestionConfig,
type DatabaseInfo,
type ExtendOnboardingContent,
type TimeCapsuleConfig,
type TTSInfo,
} from '../../types/skill';
export const DEFAULT_KNOWLEDGE_CONFIG = () => {
const baseConfig = {
top_k: 3,
min_score: 0.5,
auto: true,
search_strategy: 0,
show_source: false,
};
return baseConfig;
};
export const DEFAULT_BOT_NODE_SUGGESTION_CONFIG = (): BotSuggestionConfig => ({
suggest_reply_mode: SuggestReplyMode.UseOriginBotMode,
customized_suggest_prompt: '',
});
export const DEFAULT_SUGGESTION_PROMPT = () =>
IS_OVERSEA
? I18n.t('bot_suggestion_customize_default_gpt')
: I18n.t('bot_suggestion_customize_default_seed');
export const DEFAULT_ONBOARDING_CONFIG = (): ExtendOnboardingContent => ({
prologue: '',
suggested_questions: [],
suggested_questions_show_mode: SuggestedQuestionsShowMode.Random,
});
export const DEFAULT_SUGGESTION_CONFIG = (): BotSuggestionConfig => ({
suggest_reply_mode: SuggestReplyMode.WithDefaultPrompt,
customized_suggest_prompt: '',
});
export const DEFAULT_BACKGROUND_IMAGE_LIST = (): BackgroundImageInfo[] => [];
export const DEFAULT_DATABASE = (): DatabaseInfo => ({
tableId: '',
name: '',
desc: '',
icon_uri: '',
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
tableMemoryList: [],
});
export const DEFAULT_TTS_CONFIG = (): TTSInfo => ({
muted: false,
close_voice_call: false,
i18n_lang_voice: {},
autoplay: false,
autoplay_voice: {},
tag_list: [],
debugVoice: [],
i18n_lang_voice_str: {},
});
export const DEFAULT_TIME_CAPSULE_CONFIG = (): TimeCapsuleConfig => ({
time_capsule_mode: 0,
disable_prompt_calling: 0, // 默认支持在prompt调用
time_capsule_time_to_live: '0',
});
export const DEFAULT_SHORTCUT_CONFIG = () => ({
shortcut_list: [],
shortcut_sort: [],
});
export const DEFAULT_VOICES_INFO: () => VoicesInfo = () => ({
defaultUserInputType: undefined,
});

View File

@@ -0,0 +1,26 @@
/*
* 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 {
useBotSkillStore,
getDefaultBotSkillStore,
type BotSkillStore,
type BotSkillAction,
} from './store';
export {
DEFAULT_SUGGESTION_PROMPT,
DEFAULT_KNOWLEDGE_CONFIG,
} from './defaults';

View File

@@ -0,0 +1,314 @@
/*
* 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 { devtools, subscribeWithSelector } from 'zustand/middleware';
import { create } from 'zustand';
import { isFunction } from 'lodash-es';
import { produce } from 'immer';
import { type ShortCutStruct } from '@coze-agent-ide/tool-config';
import {
type HookInfo,
type LayoutInfo,
type BackgroundImageInfo,
type GetDraftBotInfoAgwData,
} from '@coze-arch/bot-api/playground_api';
import {
type DefaultUserInputType,
FileboxInfoMode,
type PluginApi,
} from '@coze-arch/bot-api/developer_api';
import { botInputLengthService } from '@coze-agent-ide/bot-input-length-limit';
import {
type SetterAction,
setterActionFactory,
} from '../../utils/setter-factory';
import { getPluginApisFilterExample } from '../../utils/plugin-apis';
import {
type VoicesInfo,
type BotSuggestionConfig,
type DatabaseInfo,
type DatabaseList,
type EnabledPluginApi,
type ExtendOnboardingContent,
type FileboxConfig,
type KnowledgeConfig,
type TaskManageInfo,
type TimeCapsuleConfig,
type TTSInfo,
type VariableItem,
type WorkFlowItemType,
} from '../../types/skill';
import { transformDto2Vo, transformVo2Dto } from './transform';
import {
DEFAULT_BACKGROUND_IMAGE_LIST,
DEFAULT_DATABASE,
DEFAULT_KNOWLEDGE_CONFIG,
DEFAULT_ONBOARDING_CONFIG,
DEFAULT_SHORTCUT_CONFIG,
DEFAULT_SUGGESTION_CONFIG,
DEFAULT_TIME_CAPSULE_CONFIG,
DEFAULT_TTS_CONFIG,
DEFAULT_VOICES_INFO,
} from './defaults';
export const getDefaultBotSkillStore = (): BotSkillStore => ({
pluginApis: [],
workflows: [],
knowledge: {
dataSetList: [],
dataSetInfo: DEFAULT_KNOWLEDGE_CONFIG(),
},
taskInfo: {
user_task_allowed: false,
data: [],
task_list: [],
loading: false,
},
variables: [],
database: DEFAULT_DATABASE(),
databaseList: [],
onboardingContent: DEFAULT_ONBOARDING_CONFIG(),
suggestionConfig: DEFAULT_SUGGESTION_CONFIG(),
tts: DEFAULT_TTS_CONFIG(),
voicesInfo: DEFAULT_VOICES_INFO(),
timeCapsule: DEFAULT_TIME_CAPSULE_CONFIG(),
filebox: {
mode: FileboxInfoMode.Off,
},
backgroundImageInfoList: DEFAULT_BACKGROUND_IMAGE_LIST(),
shortcut: DEFAULT_SHORTCUT_CONFIG(),
layoutInfo: {},
devHooks: {},
});
/** Persona & Prompt 区域 */
export interface BotSkillStore {
// region Bot 和 Agent 维度共有 skills
/** 已选的 plugin api */
pluginApis: EnabledPluginApi[];
/** 已选 workflow */
workflows: WorkFlowItemType[];
/** Knowledge 配置 */
knowledge: KnowledgeConfig;
// endregion
// region Bot 维度独有 skills
/**
* task 配置
*
* 不含已添加的 task已添加的在组件内独立管理
*/
taskInfo: TaskManageInfo;
/**
* variable 默认值配置
*
* 不含右上角现值,现值为打开弹窗后请求获得的组件状态
*/
variables: VariableItem[];
/**
* database 默认值配置
*
* 不含右上角现值,现值为打开弹窗后请求获得的组件状态
*/
database: DatabaseInfo;
/**
* database 多表默认值配置
*
* 不含右上角现值,现值为打开弹窗后请求获得的组件状态
*/
databaseList: DatabaseList;
/** 开场白配置 */
onboardingContent: ExtendOnboardingContent;
/** 用户问题建议配置 */
suggestionConfig: BotSuggestionConfig;
// endregion
/** 文字转语音 */
tts: TTSInfo;
/** 语音设置 上面 tts 在命名和含义划分上已经不准确了 但是牵扯甚广 */
voicesInfo: VoicesInfo;
// 时间胶囊
timeCapsule: TimeCapsuleConfig;
filebox: FileboxConfig;
// 聊天背景图
backgroundImageInfoList: BackgroundImageInfo[];
// 快捷指令
shortcut: ShortCutStruct;
// hooks
devHooks?: HookInfo;
layoutInfo: LayoutInfo;
}
export interface BotSkillAction {
setBotSkill: SetterAction<BotSkillStore>;
setBotSkillByImmer: (update: (state: BotSkillStore) => void) => void;
updateSkillPluginApis: (pluginApis: PluginApi[]) => void;
updateSkillWorkflows: (workflows: WorkFlowItemType[]) => void;
updateSkillKnowledgeDatasetList: (
dataSetList: KnowledgeConfig['dataSetList'],
) => void;
updateSkillKnowledgeDatasetInfo: (
dataSetInfo: KnowledgeConfig['dataSetInfo'],
) => void;
updateSkillTaskInfo: (taskInfo: Partial<TaskManageInfo>) => void;
updateSkillDatabase: (database: Partial<DatabaseInfo>) => void;
updateSkillDatabaseList: (database: DatabaseList) => void;
updateSkillOnboarding: (
onboarding:
| Partial<ExtendOnboardingContent>
| ((prev: ExtendOnboardingContent) => Partial<ExtendOnboardingContent>),
) => void;
updateSkillLayoutInfo: (layoutInfo: LayoutInfo) => void;
setBackgroundImageInfoList: (params: BackgroundImageInfo[]) => void;
setSuggestionConfig: (config: Partial<BotSuggestionConfig>) => void;
setDefaultUserInputType: (type: DefaultUserInputType) => void;
transformDto2Vo: typeof transformDto2Vo;
transformVo2Dto: typeof transformVo2Dto;
initStore: (botData: GetDraftBotInfoAgwData) => void;
clear: () => void;
}
export const useBotSkillStore = create<BotSkillStore & BotSkillAction>()(
devtools(
subscribeWithSelector((set, get) => ({
...getDefaultBotSkillStore(),
setBotSkill: setterActionFactory<BotSkillStore>(set),
setBotSkillByImmer: update =>
set(produce<BotSkillStore>(botSkill => update(botSkill))),
updateSkillPluginApis: (pluginApis: PluginApi[]) => {
set(s => ({
...s,
pluginApis: getPluginApisFilterExample(pluginApis),
}));
},
updateSkillWorkflows: workflows => set(s => ({ ...s, workflows })),
updateSkillKnowledgeDatasetList: dataSetList =>
set(
produce<BotSkillStore>(s => {
s.knowledge.dataSetList = dataSetList;
}),
),
updateSkillKnowledgeDatasetInfo: dataSetInfo =>
set(
produce<BotSkillStore>(s => {
s.knowledge.dataSetInfo = dataSetInfo;
}),
),
updateSkillTaskInfo: taskInfo =>
set(s => ({
...s,
taskInfo: { ...s.taskInfo, ...taskInfo },
})),
updateSkillDatabase: database =>
set(s => ({
...s,
database: { ...s.database, ...database },
})),
updateSkillDatabaseList: databaseList =>
set(
produce<BotSkillStore>(s => {
s.databaseList = databaseList;
}),
),
updateSkillOnboarding: update =>
set(s => ({
...s,
onboardingContent: {
...s.onboardingContent,
...(isFunction(update) ? update(s.onboardingContent) : update),
},
})),
updateSkillLayoutInfo: layoutInfo => {
set(s => ({
...s,
layoutInfo,
}));
},
setSuggestionConfig: config =>
set(s => ({
...s,
suggestionConfig: { ...s.suggestionConfig, ...config },
})),
setBackgroundImageInfoList: config =>
set(s => ({
...s,
backgroundImageInfoList: [...config],
})),
setDefaultUserInputType: inputType =>
set(
state =>
produce(state, draft => {
draft.voicesInfo.defaultUserInputType = inputType;
}),
false,
'setDefaultUserInputType',
),
transformDto2Vo,
transformVo2Dto,
initStore: botData => {
const { bot_info: botInfo, bot_option_data: optionData } = botData;
set({
pluginApis: transformDto2Vo.plugin(
botInfo?.plugin_info_list,
optionData?.plugin_detail_map,
optionData?.plugin_api_detail_map,
),
workflows: transformDto2Vo.workflow(
botInfo?.workflow_info_list,
optionData?.workflow_detail_map,
),
knowledge: transformDto2Vo.knowledge(
botInfo?.knowledge,
optionData?.knowledge_detail_map,
),
taskInfo: transformDto2Vo.task(botInfo?.task_info),
variables: transformDto2Vo.variables(botInfo?.variable_list),
databaseList: transformDto2Vo.databaseList(botInfo?.database_list),
timeCapsule: transformDto2Vo.timeCapsule(
botInfo?.bot_tag_info?.time_capsule_info,
),
filebox: transformDto2Vo.filebox(botInfo?.filebox_info),
onboardingContent:
botInputLengthService.sliceWorkInfoOnboardingByMaxLength(
transformDto2Vo.onboarding(botInfo?.onboarding_info),
),
suggestionConfig: transformDto2Vo.suggestionConfig(
botInfo?.suggest_reply_info,
),
tts: transformDto2Vo.tts(botInfo?.voices_info),
voicesInfo: transformDto2Vo.voicesInfo(botInfo.voices_info),
backgroundImageInfoList: botInfo?.background_image_info_list ?? [],
shortcut: transformDto2Vo.shortcut(
botInfo?.shortcut_sort ?? [],
optionData?.shortcut_command_list,
),
devHooks: transformDto2Vo.hookInfo(botInfo?.hook_info),
layoutInfo: transformDto2Vo.layoutInfo(botInfo?.layout_info),
});
},
clear: () => {
set({ ...getDefaultBotSkillStore() });
},
})),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botDetail.botSkill',
},
),
);

View File

@@ -0,0 +1,431 @@
/*
* 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 { nanoid } from 'nanoid';
import { isNumber, mapValues } from 'lodash-es';
import { type ShortCutStruct } from '@coze-agent-ide/tool-config';
import {
type PluginStatus,
type PluginType,
} from '@coze-arch/idl/plugin_develop';
import { BotTableRWMode } from '@coze-arch/idl/memory';
import {
type Int64,
type PluginInfo,
type PluginDetal,
type PluginAPIDetal,
type WorkflowInfo,
type WorkflowDetail,
type Knowledge,
type KnowledgeDetail,
type TaskInfo,
type Variable,
type Database,
type TimeCapsuleInfo,
type OnboardingInfo,
type SuggestReplyInfo,
type VoicesInfo as IDLVoicesInfo,
type FileboxInfo,
FileboxInfoMode,
TimeCapsuleMode,
type ShortcutCommand,
type BotInfoForUpdate,
type SuggestReplyMode as SuggestReplyModeFromPlayground,
type HookInfo,
SuggestedQuestionsShowMode,
type LayoutInfo,
DisablePromptCalling,
} from '@coze-arch/bot-api/playground_api';
import { SuggestReplyMode } from '@coze-arch/bot-api/developer_api';
import {
type WorkFlowItemType,
type EnabledPluginApi,
type KnowledgeConfig,
type TaskManageInfo,
type VariableItem,
type TimeCapsuleConfig,
type ExtendOnboardingContent,
type BotSuggestionConfig,
type TTSInfo,
type FileboxConfig,
type DatabaseList,
type TableMemoryItem,
type VoicesInfo,
} from '../../types/skill';
import {
DEFAULT_BOT_NODE_SUGGESTION_CONFIG,
DEFAULT_KNOWLEDGE_CONFIG,
DEFAULT_SUGGESTION_CONFIG,
DEFAULT_SUGGESTION_PROMPT,
DEFAULT_TTS_CONFIG,
} from './defaults';
// 结构化 BotInfo 接口后的 数据转换
export const transformDto2Vo = {
plugin: (
data?: PluginInfo[],
plugins?: Record<Int64, PluginDetal>,
pluginsAPIs?: Record<Int64, PluginAPIDetal>,
): EnabledPluginApi[] =>
data
?.filter(i => i.plugin_id && i.api_id && plugins?.[i.plugin_id as string])
?.map(item => {
const plugin = plugins?.[item.plugin_id as string];
const api = pluginsAPIs?.[item.api_id as string];
return {
plugin_icon: plugin?.icon_url,
name: api?.name,
desc: api?.description,
plugin_id: item.plugin_id,
plugin_name: plugin?.name,
api_id: item.api_id,
parameters:
api?.parameters?.map(i => ({
...i,
// 兼容开源页接口字段命名不同
desc: i.description,
required: i.is_required,
})) || [],
is_official: plugin?.is_official,
// 这个类型历史原因 在服务端侧各服务不统一,实际业务使用为 枚举类型
plugin_type: plugin?.plugin_type as unknown as PluginType,
status: plugin?.plugin_status as unknown as PluginStatus,
};
}) ?? [],
workflow: (
data?: WorkflowInfo[],
config?: Record<Int64, WorkflowDetail>,
): WorkFlowItemType[] =>
data
?.filter(i => i.workflow_id && config?.[i.workflow_id])
?.map(item => {
const w = config?.[item.workflow_id as string];
return {
workflow_id: w?.id ?? '',
plugin_id: w?.plugin_id ?? '',
name: w?.name ?? '',
desc: w?.description ?? '',
plugin_icon: w?.icon_url ?? '',
flow_mode: item?.flow_mode,
parameters:
w?.api_detail?.parameters?.map(i => ({
...i,
desc: i.description,
required: i.is_required,
})) || [],
};
}) ?? [],
// 知识库
knowledge: (
data?: Knowledge,
config?: Record<string, KnowledgeDetail>,
): KnowledgeConfig => {
if (!data) {
return {
dataSetList: [],
dataSetInfo: DEFAULT_KNOWLEDGE_CONFIG(),
};
} else {
const dataSetList =
data?.knowledge_info
?.filter(i => i.id && config?.[i.id as string])
?.map(item => {
const k = config?.[item.id as string];
return {
id: k?.id,
name: k?.name,
avatar_url: k?.icon_url,
icon_url: k?.icon_url,
dataset_id: k?.id,
};
}) ?? [];
return {
dataSetList,
dataSetInfo: {
min_score: data?.min_score ?? 0,
top_k: Number(data?.top_k ?? 0),
auto: Boolean(data?.auto),
search_strategy: data?.search_strategy,
no_recall_reply_mode: data?.no_recall_reply_mode,
no_recall_reply_customize_prompt:
data?.no_recall_reply_customize_prompt,
show_source: data?.show_source,
show_source_mode: data?.show_source_mode,
recall_strategy: data.recall_strategy,
},
};
}
},
task: (data?: TaskInfo): TaskManageInfo => ({
user_task_allowed: Boolean(data?.user_task_allowed),
task_list: [],
loading: false,
data: [],
}),
variables: (data?: Variable[]): VariableItem[] =>
data?.map(item => ({
id: nanoid(),
key: item.key ?? '',
description: item.description,
default_value: item.default_value,
is_system: !!item.is_system,
prompt_disabled: !!item.prompt_disabled,
is_disabled: !!item.is_disabled,
})) ?? [],
databaseList: (data?: Database[]): DatabaseList => {
const res: DatabaseList = [];
if (Array.isArray(data)) {
data.forEach(target => {
if (target?.table_id && target.field_list?.length) {
res.push({
tableId: target.table_id as string,
name: target.table_name as string,
desc: target.table_desc as string,
extra_info: {
prompt_disabled: String(target.prompt_disabled),
},
readAndWriteMode:
(target.rw_mode as BotTableRWMode) ||
BotTableRWMode.LimitedReadWrite,
tableMemoryList: (target.field_list as TableMemoryItem[])?.map(
i => ({
...i,
nanoid: nanoid(),
// 服务端 rpc 已使用 string 的 id 难以修改,这里兼容一下后续链路需要的 number
id: Number(i.id),
}),
),
});
}
});
}
return res;
},
timeCapsule: (data?: TimeCapsuleInfo): TimeCapsuleConfig => ({
//@ts-expect-error 接口枚举类型取值重定义
time_capsule_mode: data?.time_capsule_mode ?? TimeCapsuleMode.Off,
disable_prompt_calling:
data?.disable_prompt_calling ?? DisablePromptCalling.Off,
time_capsule_time_to_live: data?.time_capsule_time_to_live ?? '0',
}),
filebox: (data?: FileboxInfo): FileboxConfig => ({
mode: data?.Mode ?? FileboxInfoMode.Off,
}),
onboarding: (data?: OnboardingInfo): ExtendOnboardingContent => ({
prologue: data?.prologue ?? '',
suggested_questions_show_mode:
data?.suggested_questions_show_mode ?? SuggestedQuestionsShowMode.Random,
suggested_questions:
data?.suggested_questions?.map(item => ({
id: item,
content: item,
})) ?? [],
}),
suggestionConfig: (
data?: SuggestReplyInfo,
isBotNode = false,
): BotSuggestionConfig => {
const defaultSuggestionConfig: BotSuggestionConfig = isBotNode
? DEFAULT_BOT_NODE_SUGGESTION_CONFIG()
: DEFAULT_SUGGESTION_CONFIG();
//@ts-expect-error xxxxxxxxxxxxx SuggestReplyMode 两个文件定义不一致
const suggestionConfig: BotSuggestionConfig = isNumber(
data?.suggest_reply_mode,
)
? {
suggest_reply_mode: data?.suggest_reply_mode,
customized_suggest_prompt: data?.customized_suggest_prompt,
}
: defaultSuggestionConfig;
if (
!suggestionConfig.customized_suggest_prompt &&
suggestionConfig.suggest_reply_mode ===
SuggestReplyMode.WithCustomizedPrompt
) {
suggestionConfig.customized_suggest_prompt = DEFAULT_SUGGESTION_PROMPT();
}
return suggestionConfig;
},
tts: (ttsConfig?: IDLVoicesInfo): TTSInfo => {
if (!ttsConfig || typeof ttsConfig !== 'object') {
return DEFAULT_TTS_CONFIG();
}
if (!('muted' in ttsConfig && 'i18n_lang_voice' in ttsConfig)) {
return DEFAULT_TTS_CONFIG();
}
const isValidObject = (obj: unknown): Record<string, number> =>
obj && typeof obj === 'object' ? (obj as Record<string, number>) : {};
return {
muted: !!ttsConfig.muted,
close_voice_call: !!ttsConfig.voice_call,
i18n_lang_voice: isValidObject(ttsConfig?.i18n_lang_voice),
i18n_lang_voice_str: ttsConfig.i18n_lang_voice_str ?? {},
autoplay: !!ttsConfig.autoplay,
autoplay_voice: isValidObject(ttsConfig?.autoplay_voice),
debugVoice: [],
};
},
voicesInfo: (idlVoicesInfo: IDLVoicesInfo | undefined): VoicesInfo => ({
defaultUserInputType: idlVoicesInfo?.default_user_input_type,
}),
shortcut: (
shortcutSortList: string[],
config?: ShortcutCommand[],
): ShortCutStruct => ({
shortcut_sort: shortcutSortList,
//@ts-expect-error ShortCutCommand 前后端定义不一致,前端分化做了类型约束
shortcut_list: config,
}),
hookInfo: (data?: HookInfo): HookInfo | undefined => data,
layoutInfo: (layoutInfoFromService?: LayoutInfo): LayoutInfo => ({
workflow_id: layoutInfoFromService?.workflow_id,
plugin_id: layoutInfoFromService?.plugin_id,
}),
};
export const transformVo2Dto = {
plugin: (plugins: EnabledPluginApi[]): BotInfoForUpdate['plugin_info_list'] =>
plugins.map(plugin => ({
api_id: plugin.api_id,
plugin_id: plugin.plugin_id,
api_name: plugin.name,
})),
workflow: (
workflows: WorkFlowItemType[],
): BotInfoForUpdate['workflow_info_list'] =>
workflows.map(
w =>
({
workflow_id: w.workflow_id,
plugin_id: w.plugin_id,
flow_mode: w.flow_mode,
workflow_name: w.name,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any,
),
knowledge: (knowledge: KnowledgeConfig): BotInfoForUpdate['knowledge'] => ({
...knowledge.dataSetInfo,
knowledge_info: knowledge.dataSetList
.filter(i => !!i.dataset_id)
.map(dataset => ({
id: dataset.dataset_id,
name: dataset.name,
})),
}),
task: (task: Partial<TaskManageInfo>): BotInfoForUpdate['task_info'] => ({
user_task_allowed: task.user_task_allowed,
}),
suggestionConfig: (
suggestion: BotSuggestionConfig,
): BotInfoForUpdate['suggest_reply_info'] => ({
suggest_reply_mode:
suggestion.suggest_reply_mode as unknown as SuggestReplyModeFromPlayground,
customized_suggest_prompt: suggestion.customized_suggest_prompt,
}),
variables: (variables: VariableItem[]): BotInfoForUpdate['variable_list'] =>
variables.map(v => ({
key: v.key,
description: v.description,
default_value: v.default_value,
is_system: v.is_system,
prompt_disabled: v.prompt_disabled,
is_disabled: v.is_disabled,
})),
databaseList: (
databaseList: DatabaseList,
): BotInfoForUpdate['database_list'] =>
databaseList.map(d => ({
table_id: d.tableId,
table_name: d.name,
table_desc: d.desc,
rw_mode: d.readAndWriteMode,
field_list: d.tableMemoryList.map(f => ({
name: f.name,
desc: f.desc,
type: f.type,
must_required: f.must_required,
id: f.id?.toString(),
})),
})),
timeCapsule: (
timeCapsule: TimeCapsuleConfig,
): BotInfoForUpdate['bot_tag_info'] => ({
time_capsule_info: {
time_capsule_mode:
timeCapsule.time_capsule_mode as unknown as TimeCapsuleMode,
disable_prompt_calling:
timeCapsule.disable_prompt_calling as unknown as DisablePromptCalling,
time_capsule_time_to_live: timeCapsule.time_capsule_time_to_live,
},
}),
filebox: (filebox: FileboxConfig): BotInfoForUpdate['filebox_info'] => ({
Mode: filebox.mode,
}),
onboarding: (
data: ExtendOnboardingContent,
): BotInfoForUpdate['onboarding_info'] => ({
prologue: data.prologue,
suggested_questions_show_mode: data.suggested_questions_show_mode,
suggested_questions: data.suggested_questions
.map(i => i.content?.trim())
.filter(c => !!c),
}),
tts: (tts: Partial<TTSInfo>) => ({
muted: tts.muted,
i18n_lang_voice: tts.i18n_lang_voice,
autoplay: tts.autoplay,
autoplay_voice: tts.autoplay_voice,
voice_call: tts.close_voice_call,
i18n_lang_voice_str: tts.i18n_lang_voice_str,
}),
voicesInfo: (voicesInfo: VoicesInfo) => ({
default_user_input_type: voicesInfo.defaultUserInputType,
}),
shortcut: (shortcut: ShortCutStruct): BotInfoForUpdate['shortcut_sort'] =>
shortcut.shortcut_sort,
layoutInfo: (info: LayoutInfo): BotInfoForUpdate['layout_info'] =>
// undefined 会被 axios 过滤,这里后端需要有 key
mapValues(info, (val?: string) => val ?? ''),
};

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 { logger } from '@coze-arch/logger';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { SpaceType } from '@coze-arch/bot-api/developer_api';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { getBotDetailIsReadonly } from '../utils/get-read-only';
import { useBotInfoStore } from '../store/bot-info';
import { useCollaborationStore } from './collaboration';
export const collaborateQuota = async () => {
try {
const { botId } = useBotInfoStore.getState();
const { inCollaboration, setCollaboration } =
useCollaborationStore.getState();
const {
space: { space_type },
} = useSpaceStore.getState();
const isPersonal = space_type === SpaceType.Personal;
const isReadOnly = getBotDetailIsReadonly();
if (isReadOnly || isPersonal) {
return;
}
const { data: collaborationQuota } =
await PlaygroundApi.GetBotCollaborationQuota({
bot_id: botId,
});
setCollaboration({
// 多人协作模式,或非多人协作模式有额度时可启用
openCollaboratorsEnable:
(!inCollaboration && collaborationQuota?.open_collaborators_enable) ||
inCollaboration,
// 非多人协作模式 && 可以升级套餐,则展示升级套餐按钮
canUpgrade: collaborationQuota?.can_upgrade || false,
// 用户最大开启多人协作bot的数量限制
maxCollaborationBotCount:
collaborationQuota?.max_collaboration_bot_count || 0,
maxCollaboratorsCount: collaborationQuota?.max_collaborators_count || 0,
currentCollaborationBotCount:
collaborationQuota.current_collaboration_bot_count || 0,
});
} catch (error) {
const e = error instanceof Error ? error : new Error(error as string);
logger.error({ error: e });
}
};

View File

@@ -0,0 +1,130 @@
/*
* 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 { devtools, subscribeWithSelector } from 'zustand/middleware';
import { create } from 'zustand';
import { produce } from 'immer';
import {
Branch,
type GetDraftBotInfoAgwData,
} from '@coze-arch/idl/playground_api';
import { type BotCollaboratorStatus } from '@coze-arch/idl/developer_api';
import {
type SetterAction,
setterActionFactory,
} from '../utils/setter-factory';
export const getDefaultCollaborationStore = (): CollaborationStore => ({
inCollaboration: false,
sameWithOnline: false,
committer_name: '',
editLockStatus: EditLockStatus.Offline,
collaboratorStatus: {
commitable: false,
operateable: false,
manageable: false,
},
baseVersion: '',
branch: Branch.Base,
commit_time: '',
commit_version: '',
openCollaboratorsEnable: false,
canUpgrade: false,
currentCollaborationBotCount: 0,
maxCollaborationBotCount: 0,
maxCollaboratorsCount: 0,
});
export enum EditLockStatus {
Lose, // 无编辑锁
Holder, // 有编辑锁
Offline, // 断网状态,可编辑,但是不可保存。避免联网后覆盖掉断网期间其他页面的编辑
}
/**多人协作*/
export interface CollaborationStore {
editLockStatus: EditLockStatus;
inCollaboration: boolean;
collaboratorStatus: BotCollaboratorStatus;
sameWithOnline: boolean;
baseVersion: string;
/** for前端最近一次的提交人 */
committer_name: string;
/** 获取的是什么分支的内容 */
branch?: Branch;
/** for前端提交时间 */
commit_time: string;
commit_version: string;
/** 能否开启协作开关 false不可开启 */
openCollaboratorsEnable: boolean;
/** 是否可升级套餐 顶级付费账号不可升级 */
canUpgrade: boolean;
// 当前开启的协作bot数量
currentCollaborationBotCount: number;
/** 用户最大开启多人协作bot的数量限制 */
maxCollaborationBotCount: number;
/** 协作者数量上限 */
maxCollaboratorsCount: number;
}
export interface CollaborationAction {
setCollaboration: SetterAction<CollaborationStore>;
setCollaborationByImmer: (
update: (state: CollaborationStore) => void,
) => void;
getBaseVersion: () => string | undefined;
initStore: (data: GetDraftBotInfoAgwData) => void;
clear: () => void;
}
export const useCollaborationStore = create<
CollaborationStore & CollaborationAction
>()(
devtools(
subscribeWithSelector((set, get) => ({
...getDefaultCollaborationStore(),
setCollaboration: setterActionFactory<CollaborationStore>(set),
setCollaborationByImmer: update =>
set(produce<CollaborationStore>(state => update(state))),
getBaseVersion: () => {
const { baseVersion, inCollaboration } = get();
// FG开启且单人模式下不提供 base_version
if (!inCollaboration) {
return undefined;
}
return baseVersion;
},
initStore: info => {
set({
collaboratorStatus: info?.collaborator_status,
inCollaboration: info.in_collaboration,
baseVersion: info.commit_version,
sameWithOnline: info?.same_with_online,
committer_name: info?.committer_name,
commit_version: info?.commit_version,
branch: info?.branch,
commit_time: info?.commit_time,
});
},
clear: () => {
set({ ...getDefaultCollaborationStore() });
},
})),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botDetail.collaboration',
},
),
);

View File

@@ -0,0 +1,103 @@
/*
* 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 { devtools, subscribeWithSelector } from 'zustand/middleware';
import { create } from 'zustand';
import { produce } from 'immer';
import {
type SetterAction,
setterActionFactory,
} from '../utils/setter-factory';
export type DiffTaskType = 'prompt' | 'model' | '';
export const getDefaultDiffTaskStore = (): DiffTaskStore => ({
diffTask: '',
hasContinueTask: false,
continueTask: '',
promptDiffInfo: {
diffPromptResourceId: '',
diffMode: 'draft',
diffPrompt: '',
},
});
/** diff任务相关信息 */
export interface DiffTaskStore {
/** 当前diff任务类型 */
diffTask: DiffTaskType;
/** 是否有继续任务 */
hasContinueTask: boolean;
/** 继续任务信息 */
continueTask: DiffTaskType;
/** 当前diff任务信息 */
promptDiffInfo: {
diffPromptResourceId: string;
diffPrompt: string;
diffMode: 'draft' | 'new-diff';
};
}
export interface DiffTaskAction {
setDiffTask: SetterAction<DiffTaskStore>;
setDiffTaskByImmer: (update: (state: DiffTaskStore) => void) => void;
enterDiffMode: (props: {
diffTask: DiffTaskType;
promptDiffInfo?: {
diffPromptResourceId: string;
diffMode: 'draft' | 'new-diff';
diffPrompt: string;
};
}) => void;
exitDiffMode: () => void;
clear: () => void;
}
export const useDiffTaskStore = create<DiffTaskStore & DiffTaskAction>()(
devtools(
subscribeWithSelector((set, get) => ({
...getDefaultDiffTaskStore(),
setDiffTask: setterActionFactory<DiffTaskStore>(set),
setDiffTaskByImmer: update =>
set(produce<DiffTaskStore>(state => update(state))),
enterDiffMode: ({ diffTask, promptDiffInfo }) => {
set(
produce<DiffTaskStore>(state => {
state.diffTask = diffTask;
}),
false,
'enterDiffMode',
);
if (diffTask === 'prompt' && promptDiffInfo) {
get().setDiffTaskByImmer(state => {
state.promptDiffInfo = promptDiffInfo;
});
}
},
exitDiffMode: () => {
get().clear();
},
clear: () => {
set({ ...getDefaultDiffTaskStore() }, false, 'clear');
},
})),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botDetail.diffTask',
},
),
);

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 { devtools, subscribeWithSelector } from 'zustand/middleware';
import { create } from 'zustand';
import { produce } from 'immer';
import { type TaskNotice, type PicTask } from '@coze-arch/idl/playground_api';
import {
type GenerateImageState,
type GenerateImageAction,
type GenerateAvatarModal,
GenerateType,
DotStatus,
type GenerateBackGroundModal,
} from '../types/generate-image';
export const DEFAULT_BOT_GENERATE_AVATAR_MODAL = (): GenerateAvatarModal => ({
visible: false,
activeKey: GenerateType.Static,
selectedImage: { id: '', img_info: {} },
gif: {
loading: false,
dotStatus: DotStatus.None,
text: '',
image: { id: '', img_info: {} },
},
image: {
loading: false,
dotStatus: DotStatus.None,
text: '',
textCustomizable: false,
},
});
export const DEFAULT_BOT_GENERATE_BACKGROUND_MODAL =
(): GenerateBackGroundModal => ({
activeKey: GenerateType.Static,
selectedImage: { id: '', img_info: {} },
gif: {
loading: false,
dotStatus: DotStatus.None,
text: '',
image: { id: '', img_info: {} },
},
image: {
loading: false,
dotStatus: DotStatus.None,
promptInfo: {},
},
});
export const useGenerateImageStore = create<
GenerateImageState & GenerateImageAction
>()(
devtools(
subscribeWithSelector(set => ({
imageList: [],
noticeList: [],
generateAvatarModal: DEFAULT_BOT_GENERATE_AVATAR_MODAL(),
generateBackGroundModal: DEFAULT_BOT_GENERATE_BACKGROUND_MODAL(),
clearGenerateImageStore: () => {
set({
imageList: [],
noticeList: [],
generateAvatarModal: DEFAULT_BOT_GENERATE_AVATAR_MODAL(),
generateBackGroundModal: DEFAULT_BOT_GENERATE_BACKGROUND_MODAL(),
});
},
updateImageList: (imageList: PicTask[]) => {
set(s => ({
...s,
imageList,
}));
},
pushImageList: (image: PicTask) => {
set(s => ({
...s,
imageList: [...s.imageList, image],
}));
},
updateNoticeList: (notices: TaskNotice[]) => {
set(s => ({ ...s, notices }));
},
setGenerateAvatarModal: generateAvatarModal => {
set({ generateAvatarModal });
},
resetGenerateAvatarModal: () => {
set({ generateAvatarModal: DEFAULT_BOT_GENERATE_AVATAR_MODAL() });
},
setGenerateAvatarModalByImmer: update =>
set(
produce<GenerateImageState>(({ generateAvatarModal }) =>
update(generateAvatarModal),
),
),
setGenerateBackgroundModalByImmer: update =>
set(
produce<GenerateImageState>(({ generateBackGroundModal }) =>
update(generateBackGroundModal),
),
),
})),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botDetail.botGenerateImage',
},
),
);

View File

@@ -0,0 +1,81 @@
/*
* 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 { useAuditInfoStore } from '@/store/audit-info';
import { useQueryCollectStore } from './query-collect';
import { usePersonaStore } from './persona';
import { usePageRuntimeStore } from './page-runtime';
import { useMultiAgentStore } from './multi-agent';
import { useMonetizeConfigStore } from './monetize-config-store';
import { useModelStore } from './model';
import { useManuallySwitchAgentStore } from './manually-switch-agent-store';
import { useDiffTaskStore } from './diff-task';
import { useCollaborationStore } from './collaboration';
import { useBotSkillStore } from './bot-skill';
import { useBotInfoStore } from './bot-info';
export interface BotDetailStoreSet {
usePersonaStore: typeof usePersonaStore;
useQueryCollectStore: typeof useQueryCollectStore;
useMultiAgentStore: typeof useMultiAgentStore;
useModelStore: typeof useModelStore;
useBotSkillStore: typeof useBotSkillStore;
useBotInfoStore: typeof useBotInfoStore;
useCollaborationStore: typeof useCollaborationStore;
usePageRuntimeStore: typeof usePageRuntimeStore;
useMonetizeConfigStore: typeof useMonetizeConfigStore;
useManuallySwitchAgentStore: typeof useManuallySwitchAgentStore;
useDiffTaskStore: typeof useDiffTaskStore;
}
interface UseBotDetailStoreSet {
getStore: () => BotDetailStoreSet;
clear: () => void;
}
export const useBotDetailStoreSet: UseBotDetailStoreSet = {
getStore() {
return {
usePersonaStore,
useQueryCollectStore,
useMultiAgentStore,
useModelStore,
useBotSkillStore,
useBotInfoStore,
useCollaborationStore,
usePageRuntimeStore,
useMonetizeConfigStore,
useManuallySwitchAgentStore,
useAuditInfoStore,
useDiffTaskStore,
};
},
clear() {
usePersonaStore.getState().clear();
useQueryCollectStore.getState().clear();
useMultiAgentStore.getState().clear();
useModelStore.getState().clear();
useBotSkillStore.getState().clear();
useBotInfoStore.getState().clear();
useCollaborationStore.getState().clear();
usePageRuntimeStore.getState().clear();
useMonetizeConfigStore.getState().reset();
useManuallySwitchAgentStore.getState().clearAgentId();
useAuditInfoStore.getState().clear();
useDiffTaskStore.getState().clear();
},
};

View File

@@ -0,0 +1,56 @@
/*
* 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.
*/
/**
* 用来满足一个神奇的功能
* multi agent 模式下 正在回复中
* 用户手动切换了 agent
* 基于新的 agent 重新生成对话
* 需要记录 agent 的切换是「手动」|「自动」
*/
/**
* !! 不和 Bot Detail 搅合在一起了。
*/
import { devtools } from 'zustand/middleware';
import { create } from 'zustand';
export interface ManuallySwitchAgentState {
agentId: string | null;
}
export interface ManuallySwitchAgentAction {
recordAgentIdOnManuallySwitchAgent: (agentId: string) => void;
clearAgentId: () => void;
}
export const useManuallySwitchAgentStore = create<
ManuallySwitchAgentAction & ManuallySwitchAgentState
>()(
devtools(
set => ({
agentId: null,
recordAgentIdOnManuallySwitchAgent: agentId => {
set({ agentId }, false, 'recordAgentIdOnManuallySwitchAgent');
},
clearAgentId: () => {
set({ agentId: null }, false, 'clearAgentId');
},
}),
{ enabled: IS_DEV_MODE, name: 'botStudio.manuallySwitchAgentStore' },
),
);

View File

@@ -0,0 +1,158 @@
/*
* 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 { devtools, subscribeWithSelector } from 'zustand/middleware';
import { create } from 'zustand';
import { produce } from 'immer';
import {
type BotInfoForUpdate,
type ContextMode,
type GetDraftBotInfoAgwData,
} from '@coze-arch/idl/playground_api';
import {
ContextContentType,
type Model,
type ModelInfo,
type ModelInfo as ModelInfoConfig,
} from '@coze-arch/bot-api/developer_api';
import {
type SetterAction,
setterActionFactory,
} from '../utils/setter-factory';
import type { BotDetailModel } from '../types/model';
export const DEFAULT_MODEL_INFO = (): ModelInfo => ({
model: '',
temperature: 0,
max_tokens: 4096,
top_p: 0,
frequency_penalty: 0,
presence_penalty: 0,
prompt_id: 0,
ShortMemPolicy: {
ContextContentType: ContextContentType.USER_RES,
},
card_ids: [],
});
export const getDefaultModelStore = (): ModelStore => ({
config: {
model: '',
temperature: 0,
max_tokens: 4096,
top_p: 0,
frequency_penalty: 0,
presence_penalty: 0,
prompt_id: 0,
ShortMemPolicy: {
ContextContentType: ContextContentType.USER_RES,
},
card_ids: [],
},
modelList: [],
});
/** Persona & Prompt 区域 */
export interface ModelStore {
config: ModelInfoConfig;
/** 全部可选模型 */
modelList: Model[];
}
export interface ModelAction {
setModel: SetterAction<ModelStore>;
setModelByImmer: (update: (state: ModelStore) => void) => void;
transformDto2Vo: (
botData: GetDraftBotInfoAgwData,
) => BotDetailModel['config'];
transformVo2Dto: (
model: BotDetailModel['config'],
) => BotInfoForUpdate['model_info'];
initStore: (botData: GetDraftBotInfoAgwData) => void;
clear: () => void;
}
export const useModelStore = create<ModelStore & ModelAction>()(
devtools(
subscribeWithSelector((set, get) => ({
...getDefaultModelStore(),
setModel: setterActionFactory<ModelStore>(set),
setModelByImmer: update =>
set(
produce<ModelStore>(model => update(model)),
false,
'setModelByImmer',
),
transformDto2Vo: botData => {
const modelInfo = botData.bot_info.model_info;
const config = botData.bot_option_data?.model_detail_map;
return {
model: modelInfo?.model_id,
temperature: modelInfo?.temperature,
max_tokens: modelInfo?.max_tokens,
top_p: modelInfo?.top_p,
frequency_penalty: modelInfo?.frequency_penalty,
presence_penalty: modelInfo?.presence_penalty,
ShortMemPolicy: {
ContextContentType: modelInfo?.short_memory_policy
?.context_mode as unknown as ContextContentType,
HistoryRound: modelInfo?.short_memory_policy?.history_round,
},
model_name:
modelInfo?.model_id && config
? config[modelInfo.model_id]?.model_name
: '',
model_style: modelInfo?.model_style,
response_format: modelInfo?.response_format,
};
},
transformVo2Dto: model =>
model?.model
? {
model_id: model.model,
temperature: model.temperature,
max_tokens: model.max_tokens,
top_p: model.top_p,
presence_penalty: model.presence_penalty,
frequency_penalty: model.frequency_penalty,
short_memory_policy: {
history_round: model?.ShortMemPolicy?.HistoryRound,
context_mode: model?.ShortMemPolicy
?.ContextContentType as unknown as ContextMode,
},
response_format: model.response_format,
model_style: model.model_style,
}
: {},
initStore: botData => {
const { transformDto2Vo } = get();
const { bot_info, bot_option_data } = botData;
bot_info?.model_info && bot_option_data?.model_detail_map
? set({
config: transformDto2Vo(botData),
})
: set({ config: DEFAULT_MODEL_INFO() });
},
clear: () => {
set({ ...getDefaultModelStore() });
},
})),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botDetail.model',
},
),
);

View File

@@ -0,0 +1,74 @@
/*
* 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 { devtools } from 'zustand/middleware';
import { create } from 'zustand';
import { isNil } from 'lodash-es';
import {
type BotMonetizationConfigData,
BotMonetizationRefreshPeriod,
} from '@coze-arch/idl/benefit';
export interface MonetizeConfigState {
/** 是否开启付费 */
isOn: boolean;
/** 开启付费后,用户免费体验的次数 */
freeCount: number;
/** 刷新周期 */
refreshCycle: BotMonetizationRefreshPeriod;
}
export interface MonetizeConfigAction {
setIsOn: (isOn: boolean) => void;
setFreeCount: (freeCount: number) => void;
setRefreshCycle: (refreshCycle: BotMonetizationRefreshPeriod) => void;
initStore: (data: BotMonetizationConfigData) => void;
reset: () => void;
}
const DEFAULT_STATE: () => MonetizeConfigState = () => ({
isOn: false,
freeCount: 0,
refreshCycle: 1,
});
export type MonetizeConfigStore = MonetizeConfigState & MonetizeConfigAction;
export const useMonetizeConfigStore = create<MonetizeConfigStore>()(
devtools(
(set, get) => ({
...DEFAULT_STATE(),
setIsOn: isOn => set({ isOn }),
setFreeCount: freeCount => set({ freeCount }),
setRefreshCycle: refreshCycle => set({ refreshCycle }),
initStore: data => {
const { setIsOn, setFreeCount, setRefreshCycle } = get();
setIsOn(isNil(data?.is_enable) ? true : data.is_enable);
setFreeCount(
isNil(data?.free_chat_allowance_count)
? 0
: data.free_chat_allowance_count,
);
setRefreshCycle(
data?.refresh_period ?? BotMonetizationRefreshPeriod.Never,
);
},
reset: () => set(DEFAULT_STATE()),
}),
{ enabled: IS_DEV_MODE, name: 'botStudio.monetizeConfig' },
),
);

View File

@@ -0,0 +1,23 @@
/*
* 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 type { AgentBizInfo } from '../../types/agent';
export const DEFAULT_AGENT_BIZ_INFO = (): AgentBizInfo => ({});
export const DEFAULT_AGENT_DESCRIPTION = () =>
I18n.t('multiagent_node_scenarios_context_default');

View File

@@ -0,0 +1,18 @@
/*
* 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 { useMultiAgentStore, type MultiAgentStore } from './store';
export { DEFAULT_AGENT_BIZ_INFO } from './defaults';

View File

@@ -0,0 +1,573 @@
/*
* 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 { devtools, subscribeWithSelector } from 'zustand/middleware';
import { create } from 'zustand';
import { nanoid } from 'nanoid';
import { isEqual, uniqWith } from 'lodash-es';
import { produce } from 'immer';
import { withSlardarIdButton } from '@coze-studio/bot-utils';
import {
type BotOptionData,
type bot_common,
type AgentReferenceInfo,
type Agent as AgentFromPlayground,
BotMode,
type GetDraftBotInfoAgwData,
} from '@coze-arch/idl/playground_api';
import { I18n } from '@coze-arch/i18n';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { SpaceApiV2 } from '@coze-arch/bot-space-api';
import { UIToast } from '@coze-arch/bot-semi';
import {
AgentType,
AgentVersionCompat,
MultiAgentSessionType,
type PluginApi,
} from '@coze-arch/bot-api/developer_api';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { LineType } from '@flowgram-adapter/free-layout-editor';
import type { WorkflowEdgeJSON } from '@flowgram-adapter/free-layout-editor';
import type { IPoint } from '@flowgram-adapter/common';
import { type SetterAction, setterActionFactory } from '@/utils/setter-factory';
import { getPluginApisFilterExample } from '@/utils/plugin-apis';
import {
findAgentByNextIntentID,
findFirstAgent,
findFirstAgentId,
findTargetAgent,
} from '@/utils/find-agent';
import type { BotDetailSkill, KnowledgeConfig } from '@/types/skill';
import { useManuallySwitchAgentStore } from '../manually-switch-agent-store';
import { useCollaborationStore } from '../collaboration';
import { useBotInfoStore } from '../bot-info';
import {
type Agent,
type ChatModeConfig,
type DraftBotVo,
type MultiSheetViewOpenState,
} from '../../types/agent';
import { transformDto2Vo, transformVo2Dto } from './transform';
export interface MultiAgentStore {
agents: Agent[];
edges: WorkflowEdgeJSON[];
connector_type: LineType;
/** 用于保存 bot 类型节点的 bot 信息 */
botAgentInfos: DraftBotVo[];
/**
* 会话接管方式配置
* 默认为 flow 模式
*/
chatModeConfig: ChatModeConfig;
/** 当前agent id **/
currentAgentID: string;
/**muti 左右展开状态**/
multiSheetViewOpen: MultiSheetViewOpenState;
}
export const getDefaultMultiAgentStore = (): MultiAgentStore => ({
agents: [],
edges: [],
connector_type: LineType.BEZIER,
currentAgentID: '',
botAgentInfos: [],
multiSheetViewOpen: {
left: true,
right: true,
},
chatModeConfig: {
type: MultiAgentSessionType.Host,
currentHostId: '',
},
});
export interface MultiAgentAction {
setMultiAgent: SetterAction<MultiAgentStore>;
setMultiAgentByImmer: (update: (state: MultiAgentStore) => void) => void;
setMultiSheetViewOpen: (state: Partial<MultiSheetViewOpenState>) => void;
updatedCurrentAgentIdWithConnectStart: () => void;
/** 重置 host 节点为 start 相连的节点 */
resetHostAgent: () => void;
/**
* 设置sourceAgentId的portId的intent的next_agent_id为nextAgentId
*
* @deprecated 这是旧版画布的方法,新版为 addAgentIntent
*/
setAgentIntentNextID: (
sourceAgentId?: string,
portId?: string,
agentId?: string,
) => void;
addAgentIntent: (sourceAgentId: string, targetAgentId: string) => void;
deleteAgentIntent: (sourceAgentId: string, targetAgentId: string) => void;
/**
* 根据agentId找到当前agent的source的agent也就是上游agent
* 将上游agent中找到某个intentnext_agent_id === agentId。将该intent的next_agent_id清空
*/
clearEdgesByTargetAgentId: (agentId?: string) => void;
updateAgentSkillKnowledgeDatasetInfo: (
agentId: string,
dataSetInfo: KnowledgeConfig['dataSetInfo'],
) => void;
updateAgentSkillPluginApis: (
agentId: string,
pluginApis: Array<PluginApi>,
) => void;
addAgent2Store: (
agentInfo: bot_common.Agent,
optionData?: BotOptionData,
) => Agent;
addAgent: (config: {
/** @default AgentType.LLM_Agent */
type?: AgentType;
position?: IPoint;
/** 是否使用struct版本 - 便于写单测即将删除*/
structFlag?: boolean;
}) => Promise<Agent | undefined>;
batchAddBotAgent: (config: {
bots: AgentReferenceInfo[];
positions: IPoint[];
/** 是否使用struct版本 - 便于写单测即将删除*/
structFlag?: boolean;
}) => Promise<Agent[]>;
updateBotNodeInfo: (
agents: AgentFromPlayground[],
) => Promise<void> | undefined;
copyAgent: (agentId: string) => Promise<Agent | undefined>;
removeAgentSkillItem: (
agentId: string,
type: keyof Pick<BotDetailSkill, 'pluginApis' | 'workflows' | 'knowledge'>,
apiId?: string,
) => void;
/**
* 清除一条 intent 的 next_id也就是 edge
*
* - Q1为什么不直接使用 intent id 来找 intent
* - A1将一条已有的连线拖拽连接到另一个节点时SDK 会先触发这个 intent 的 addEdge 事件,随后再触发 deleteEdge 事件。
* 导致 deleteEdge 事件会通过 intent id 覆盖 addEdge 刚刚更新过的 intent。
*
* - Q2那通过 targetAgentId 来找 intent 不够吗,为什么还需要 intent id
* - A2当同一个节点的两个 intent 都指向同一个目标,这时删除其中一条连线,无法确定删除的是哪条,必须配合 intent id 来判断
*
* @deprecated 旧版画布的方法,新版为 deleteAgentIntent
*/
clearIntentNextId: (
sourceAgentId: string,
targetAgentId: string,
intentId: string,
) => void;
transformDto2Vo: typeof transformDto2Vo;
transformVo2Dto: typeof transformVo2Dto;
initStore: (botData: GetDraftBotInfoAgwData) => void;
clear: () => void;
}
export const useMultiAgentStore = create<MultiAgentStore & MultiAgentAction>()(
devtools(
// eslint-disable-next-line @coze-arch/max-line-per-function
subscribeWithSelector((set, get) => ({
...getDefaultMultiAgentStore(),
setMultiAgent: setterActionFactory<MultiAgentStore>(set),
setMultiAgentByImmer: update =>
set(produce<MultiAgentStore>(multiAgent => update(multiAgent))),
setMultiSheetViewOpen: (state: Partial<MultiSheetViewOpenState>) => {
set(s => ({
...s,
multiSheetViewOpen: {
...s.multiSheetViewOpen,
...state,
},
}));
},
updatedCurrentAgentIdWithConnectStart: () => {
const firstAgent = findFirstAgent(get());
const newAgentId = firstAgent?.id;
useManuallySwitchAgentStore.getState().clearAgentId();
if (newAgentId) {
set(
produce<MultiAgentStore>(state => {
state.currentAgentID = newAgentId;
}),
);
}
},
resetHostAgent: () => {
const firstAgentId = findFirstAgentId(get());
if (!firstAgentId) {
return;
}
set(
produce<MultiAgentStore>(multiAgent => {
if (multiAgent.chatModeConfig.type !== MultiAgentSessionType.Host) {
return;
}
multiAgent.chatModeConfig.currentHostId = firstAgentId;
}),
);
},
setAgentIntentNextID: (
sourceAgentId?: string,
portId?: string,
agentId?: string,
) => {
set(
produce<MultiAgentStore>(state => {
const { agents } = state;
const sourceAgent = findTargetAgent(agents, sourceAgentId);
if (sourceAgent) {
const targetIntent = sourceAgent.intents?.find(
item => item.intent_id === portId,
);
if (targetIntent && agentId) {
targetIntent.next_agent_id = agentId;
}
}
}),
);
},
clearIntentNextId: (
sourceAgentId: string,
targetAgentId: string,
intentId: string,
) => {
set(
produce<MultiAgentStore>(state => {
const sourceAgent = findTargetAgent(state.agents, sourceAgentId);
const sourceIntent = sourceAgent?.intents?.find(
i =>
i.next_agent_id === targetAgentId && i.intent_id === intentId,
);
if (!sourceIntent) {
return;
}
sourceIntent.next_agent_id = undefined;
}),
);
},
addAgentIntent: (sourceAgentId, targetAgentId) => {
set(
produce<MultiAgentStore>(({ agents }) => {
const sourceAgent = findTargetAgent(agents, sourceAgentId);
if (!sourceAgent) {
return;
}
const newIntent = {
intent_id: nanoid(),
next_agent_id: targetAgentId,
};
switch (sourceAgent.agent_type) {
case AgentType.Global_Agent:
if (sourceAgent.intents?.[0]) {
sourceAgent.intents[0].next_agent_id = targetAgentId;
} else {
sourceAgent.intents = [newIntent];
}
break;
default:
if (sourceAgent.intents) {
sourceAgent.intents.push(newIntent);
} else {
sourceAgent.intents = [newIntent];
}
}
}),
);
},
deleteAgentIntent: (sourceAgentId, targetAgentId) =>
set(
produce<MultiAgentStore>(({ agents }) => {
const sourceAgent = findTargetAgent(agents, sourceAgentId);
if (!sourceAgent) {
return;
}
switch (sourceAgent.agent_type) {
case AgentType.Global_Agent:
if (sourceAgent.intents?.[0]) {
sourceAgent.intents[0].next_agent_id = undefined;
} else {
sourceAgent.intents = [{ intent_id: nanoid() }];
}
break;
default:
sourceAgent.intents =
sourceAgent.intents?.filter(
intent => intent.next_agent_id !== targetAgentId,
) || [];
}
}),
),
clearEdgesByTargetAgentId: (targetAgentId?: string) => {
set(
produce<MultiAgentStore>(state => {
const { agents } = state;
const sourceAgent = findAgentByNextIntentID(agents, targetAgentId);
if (sourceAgent) {
const { intents } = sourceAgent;
// 执行第一步指令。将上游agent的next_agent_id清空
intents?.forEach(item => {
if (item.next_agent_id === targetAgentId) {
item.next_agent_id = undefined;
}
});
}
}),
);
},
updateAgentSkillKnowledgeDatasetInfo: (agentId, dataSetInfo) => {
set(
produce<MultiAgentStore>(state => {
const findAgent = findTargetAgent(state.agents, agentId);
if (findAgent) {
findAgent.skills.knowledge.dataSetInfo = dataSetInfo;
}
}),
);
},
updateAgentSkillPluginApis: (agentId, pluginApis) => {
set(
produce<MultiAgentStore>(state => {
const findAgent = findTargetAgent(state.agents, agentId);
if (findAgent) {
findAgent.skills.pluginApis =
getPluginApisFilterExample(pluginApis);
}
}),
);
},
addAgent2Store: (
agentInfo: bot_common.Agent,
optionData?: BotOptionData,
) => {
const agent = transformDto2Vo.agent(optionData, agentInfo);
set(
produce<MultiAgentStore>(state => {
state.agents.push(agent);
}),
);
return agent;
},
addAgent: async ({ type = AgentType.LLM_Agent, position }) => {
const { botId } = useBotInfoStore.getState();
const { getBaseVersion, setCollaborationByImmer } =
useCollaborationStore.getState();
const createAgentParams = {
agent_type: type,
bot_id: botId,
position,
base_commit_version: getBaseVersion(),
version_compat: AgentVersionCompat.NewVersion,
};
const { data, same_with_online, branch } =
await PlaygroundApi.CreateAgentV2(createAgentParams);
if (!data) {
UIToast.error({
content: withSlardarIdButton(
I18n.t('chatflow_error_create_failed'),
),
});
return;
}
setCollaborationByImmer(state => {
state.sameWithOnline = same_with_online ?? false;
state.branch = branch;
});
return get().addAgent2Store(data);
},
batchAddBotAgent: async ({ bots, positions }) => {
const spaceId = useSpaceStore.getState().space.id as string;
const { botId } = useBotInfoStore.getState();
const { getBaseVersion, setCollaborationByImmer } =
useCollaborationStore.getState();
const { botAgentInfos } = get();
const batchCreateAgentParams = {
bot_id: botId,
agent_type: AgentType.Bot_Agent,
position: positions,
references: bots,
agent_cnt: bots.length,
base_commit_version: getBaseVersion(),
};
const [
{ data: agentInfos, same_with_online, branch },
{ data: botInfos },
] = await Promise.all([
PlaygroundApi.BatchCreateAgentV2(batchCreateAgentParams),
PlaygroundApi.MGetBotByVersion({
space_id: spaceId,
bot_versions: bots?.map(e => ({
bot_id: e.ReferenceId,
version: e.Version,
})),
}),
]);
if (
!Array.isArray(agentInfos) ||
agentInfos.length === 0 ||
!Array.isArray(botInfos) ||
botInfos.length === 0
) {
UIToast.error({
content: withSlardarIdButton(
I18n.t('chatflow_error_create_failed'),
),
});
return [] as Agent[];
}
const botInfosVo = botInfos.map(transformDto2Vo.botNodeInfo);
setCollaborationByImmer(store => {
store.sameWithOnline = same_with_online ?? false;
store.branch = branch;
});
set(
produce<MultiAgentStore>(store => {
store.botAgentInfos = uniqWith(
[...botAgentInfos, ...botInfosVo],
isEqual,
);
}),
);
return agentInfos.map(e => {
const botInfo = botInfosVo.find(b => b.id === e.reference_id);
return get().addAgent2Store({
...e,
agent_name: botInfo?.name,
icon_uri: botInfo?.icon_url,
});
});
},
copyAgent: async (agentId: string) => {
const { botId } = useBotInfoStore.getState();
const { getBaseVersion } = useCollaborationStore.getState();
const copyAgentParams = {
space_id: useSpaceStore.getState().getSpaceId(),
bot_id: botId,
base_commit_version: getBaseVersion(),
agent_id: agentId,
};
const { data, bot_option_data = {} } = await PlaygroundApi.CopyAgentV2(
copyAgentParams,
);
if (!data) {
UIToast.error({
content: withSlardarIdButton(
I18n.t('chatflow_error_create_failed'),
),
});
return;
}
return get().addAgent2Store(data, bot_option_data);
},
removeAgentSkillItem: (agentId, type, apiId) => {
set(
produce<MultiAgentStore>(s => {
const findAgent = findTargetAgent(s.agents, agentId);
if (findAgent?.skills) {
switch (type) {
case 'pluginApis': {
findAgent.skills.pluginApis =
findAgent.skills.pluginApis.filter(
item => item.api_id !== apiId,
);
break;
}
case 'workflows': {
findAgent.skills.workflows =
findAgent.skills.workflows.filter(
item => item.workflow_id !== apiId,
);
break;
}
case 'knowledge': {
findAgent.skills.knowledge.dataSetList =
findAgent.skills.knowledge.dataSetList.filter(
item => item.dataset_id !== apiId,
);
break;
}
default:
console.warn('[removeAgentSkillItem]: ?');
}
}
}),
);
},
updateBotNodeInfo: agents => {
const { setMultiAgentByImmer } = get();
const botAgents = agents.filter(
e => e.agent_type === AgentType.Bot_Agent,
);
if (Array.isArray(botAgents) && botAgents.length > 0) {
return SpaceApiV2.MGetBotByVersion({
bot_versions: botAgents?.map(e => ({
bot_id: e.reference_id,
version: e.current_version,
})),
}).then(botInfos => {
setMultiAgentByImmer(s => {
s.botAgentInfos = (botInfos.data ?? []).map(
transformDto2Vo.botNodeInfo,
);
});
});
}
},
transformDto2Vo,
transformVo2Dto,
initStore: botData => {
const { bot_info: botInfo } = botData;
const {
transformDto2Vo: transformDto2Vo4Multi,
updatedCurrentAgentIdWithConnectStart,
updateBotNodeInfo,
} = get();
const {
bot_info: { agents, multi_agent_info: multiInfo },
bot_option_data: botOpts,
} = botData;
set(
transformDto2Vo4Multi.multiAgent({
agents,
multiInfo,
botOpts,
}),
);
const isMultiAgent = botInfo?.bot_mode === BotMode.MultiMode;
if (isMultiAgent) {
// 设置初始的对话agent id
updatedCurrentAgentIdWithConnectStart();
// 获取agent节点为子bot的子bot信息并赋值入store
updateBotNodeInfo(botInfo?.agents || []);
}
},
clear: () => {
// eslint-disable-next-line max-lines
set({ ...getDefaultMultiAgentStore() });
},
})),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botDetail.multiAgent',
},
),
);

View File

@@ -0,0 +1,196 @@
/*
* 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 { nanoid } from 'nanoid';
import { omit } from 'lodash-es';
import {
type Agent as AgentFromPlayground,
type BotOptionData,
AgentType,
type DraftBotApi,
type MultiAgentInfo,
MultiAgentSessionType,
type UpdateAgentV2Request,
ReferenceUpdateType,
} from '@coze-arch/bot-api/playground_api';
import { LineType } from '@flowgram-adapter/free-layout-editor';
import { useModelStore } from '../model';
import { useBotSkillStore } from '../bot-skill';
import { findFirstAgentId } from '../../utils/find-agent';
import type { BotSuggestionConfig } from '../../types/skill';
import type { Agent, BotMultiAgent, DraftBotVo } from '../../types/agent';
import { DEFAULT_AGENT_BIZ_INFO, DEFAULT_AGENT_DESCRIPTION } from './defaults';
export const transformDto2Vo = {
agent: (botOpts?: BotOptionData, data?: AgentFromPlayground): Agent => {
const { transformDto2Vo: transformDto2Vo4BotSkill } =
useBotSkillStore.getState();
const { transformDto2Vo: transformDto2Vo4Model } = useModelStore.getState();
const model = transformDto2Vo4Model({
bot_info: {
model_info: data?.model_info,
},
bot_option_data: botOpts,
});
const prompt = data?.prompt_info?.prompt ?? '';
const pluginApis = transformDto2Vo4BotSkill.plugin(
data?.plugin_info_list,
botOpts?.plugin_detail_map,
botOpts?.plugin_api_detail_map,
);
const workflows = transformDto2Vo4BotSkill.workflow(
data?.workflow_info_list,
botOpts?.workflow_detail_map,
);
const knowledge = transformDto2Vo4BotSkill.knowledge(
data?.knowledge,
botOpts?.knowledge_detail_map,
);
const devHooks = transformDto2Vo4BotSkill.hookInfo(data?.hook_info);
return {
id: data?.agent_id ?? '',
reference_id: data?.reference_id,
reference_info_status: data?.reference_info_status,
update_type: data?.update_type,
agent_type: data?.agent_type,
name: data?.agent_name,
position: data?.agent_position,
model,
prompt,
description: data?.description || DEFAULT_AGENT_DESCRIPTION(),
// 默认的业务状态bizInfo
bizInfo: DEFAULT_AGENT_BIZ_INFO(),
system_info_all: [],
skills: {
pluginApis,
workflows,
knowledge,
...(devHooks ? { devHooks } : {}),
},
current_version: data?.current_version,
suggestion: data?.suggest_reply_info as unknown as BotSuggestionConfig,
intents: data?.intents || [],
jump_config: data?.jump_config || {},
...(data?.agent_type === AgentType.Global_Agent && {
intents: data.intents?.length
? data.intents
: [{ intent_id: nanoid() }],
}),
};
},
botNodeInfo: (bot: DraftBotApi): DraftBotVo => {
const { transformDto2Vo: transformDto2Vo4BotSkill } =
useBotSkillStore.getState();
return {
...bot,
work_info: {
suggest_reply: transformDto2Vo4BotSkill.suggestionConfig(
bot.suggest_reply,
true,
),
},
};
},
multiAgent: ({
agents,
multiInfo,
botOpts,
}: {
agents?: AgentFromPlayground[];
multiInfo?: MultiAgentInfo;
botOpts?: BotOptionData;
}): BotMultiAgent => {
const transformedAgents =
agents?.map(item => transformDto2Vo.agent(botOpts, item)) || [];
const tempEdges = transformedAgents?.flatMap(
agent =>
agent.intents?.map(intent => ({
sourceNodeID: agent.id,
targetNodeID: intent.next_agent_id || '',
sourcePortID: intent.intent_id,
})) || [],
);
return {
edges: tempEdges,
connector_type: (multiInfo?.connector_type ??
LineType.BEZIER) as LineType,
agents: transformedAgents,
botAgentInfos: [],
chatModeConfig:
multiInfo?.session_type === MultiAgentSessionType.Host
? {
type: multiInfo.session_type,
currentHostId:
findFirstAgentId({
agents: transformedAgents,
}) || '',
}
: { type: MultiAgentSessionType.Flow },
};
},
};
export const transformVo2Dto = {
agent: (targetAgent: Agent): Omit<UpdateAgentV2Request, 'bot_id'> => {
const { transformVo2Dto: transformVo2Dto4BotSkill } =
useBotSkillStore.getState();
const { transformVo2Dto: transformVo2Dto4Model } = useModelStore.getState();
return {
...omit(targetAgent, [
'skills',
'system_info_all',
'prompt',
'bizInfo',
'jump_config',
'model',
'suggestion',
]),
plugin_info_list: transformVo2Dto4BotSkill.plugin(
targetAgent?.skills?.pluginApis,
),
workflow_info_list: transformVo2Dto4BotSkill.workflow(
targetAgent?.skills?.workflows,
),
knowledge: transformVo2Dto4BotSkill.knowledge(
targetAgent?.skills?.knowledge,
),
suggest_reply_info: transformVo2Dto4BotSkill.suggestionConfig(
targetAgent?.suggestion,
),
hook_info: targetAgent?.skills?.devHooks,
model_info: transformVo2Dto4Model(targetAgent?.model),
prompt_info: {
prompt: targetAgent.prompt,
},
jump_config: targetAgent.jump_config,
current_version:
targetAgent.update_type === ReferenceUpdateType.AutoUpdate
? // 如果当前agent是自动更新则将current_version置为"0"
'0'
: targetAgent.current_version,
};
},
};

View File

@@ -0,0 +1,34 @@
/*
* 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 TabDisplayItems, TabStatus } from '@coze-arch/idl/developer_api';
export const DEFAULT_BOT_SKILL_BLOCK_COLLAPSIBLE_STATE =
(): TabDisplayItems => ({
plugin_tab_status: TabStatus.Default,
workflow_tab_status: TabStatus.Default,
imageflow_tab_status: TabStatus.Default,
knowledge_tab_status: TabStatus.Default,
database_tab_status: TabStatus.Default,
variable_tab_status: TabStatus.Default,
opening_dialog_tab_status: TabStatus.Default,
scheduled_task_tab_status: TabStatus.Default,
suggestion_tab_status: TabStatus.Default,
tts_tab_status: TabStatus.Default,
filebox_tab_status: TabStatus.Default,
background_image_tab_status: TabStatus.Default,
shortcut_tab_status: TabStatus.Default,
});

View File

@@ -0,0 +1,18 @@
/*
* 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 { usePageRuntimeStore, type PageRuntime } from './store';
export { DEFAULT_BOT_SKILL_BLOCK_COLLAPSIBLE_STATE } from './defaults';

View File

@@ -0,0 +1,189 @@
/*
* 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 { devtools, subscribeWithSelector } from 'zustand/middleware';
import { create } from 'zustand';
import { size } from 'lodash-es';
import { produce } from 'immer';
import dayjs from 'dayjs';
import { type GetDraftBotInfoAgwData } from '@coze-arch/idl/playground_api';
import { type BotPageFromEnum } from '@coze-arch/bot-typings/common';
import { SpaceApi } from '@coze-arch/bot-space-api';
import {
type GetDraftBotDisplayInfoResponse,
type TabDisplayItems,
} from '@coze-arch/bot-api/developer_api';
import { DeveloperApi } from '@coze-arch/bot-api';
import { useBotInfoStore } from '../bot-info';
import {
type SetterAction,
setterActionFactory,
} from '../../utils/setter-factory';
import { DEFAULT_BOT_SKILL_BLOCK_COLLAPSIBLE_STATE } from './defaults';
interface SavingInfo {
saving: boolean;
time: string;
debouncing?: boolean;
scopeKey?: string;
triggerType?: string;
}
export const getDefaultPageRuntimeStore = (): PageRuntime => ({
init: false,
isSelf: false,
isPreview: false,
editable: false,
savingInfo: {
saving: false,
time: dayjs().format('HH:mm:ss'),
debouncing: false,
scopeKey: '',
triggerType: '',
},
historyVisible: false,
botSkillBlockCollapsibleState: {},
grabPluginId: '',
hasUnpublishChange: false,
});
// bot的编辑器状态控制
export interface PageRuntime {
/** 初始化 **/
init: boolean;
/** 当前用户是否是bot的创建者 **/
isSelf: boolean;
/** 是否是预览状态isPreview = typeof version !== 'undefined'; **/
isPreview: boolean;
/** 服务端透传 **/
editable: boolean;
/**控制bot 历史版本展示 **/
historyVisible?: boolean;
/** 记录用户主动展开/收起bot能力模块的状态 **/
botSkillBlockCollapsibleState: TabDisplayItems;
/** 页面来源 **/
pageFrom?: BotPageFromEnum;
/** 保存信息 **/
savingInfo: SavingInfo;
/** 划词插件id, 一个chat-area一个 **/
grabPluginId: string;
/** 是否有未发布的修改, header头部展示**/
hasUnpublishChange: boolean;
}
export type InitStoreData = GetDraftBotInfoAgwData & { customVersion?: string };
export interface PageRuntimeAction {
setPageRuntimeBotInfo: SetterAction<PageRuntime>;
setPageRuntimeByImmer: (update: (state: PageRuntime) => void) => void;
getBotSkillBlockCollapsibleState: () => Promise<void>;
setBotSkillBlockCollapsibleState: (
$params: TabDisplayItems,
disableUpdateService?: boolean,
) => void;
getIsPreview: (version?: string) => boolean;
initStore: (data: InitStoreData) => void;
clear: () => void;
}
export const usePageRuntimeStore = create<PageRuntime & PageRuntimeAction>()(
devtools(
subscribeWithSelector((set, get) => ({
...getDefaultPageRuntimeStore(),
setPageRuntimeBotInfo: setterActionFactory<PageRuntime>(set),
setPageRuntimeByImmer: update =>
set(produce<PageRuntime>(state => update(state))),
/**
* 获取用户主动展开/收起bot能力模块的状态
* ⚠️ 仅在首次打开本人 bot 编辑页时调用
* @see
*/
getBotSkillBlockCollapsibleState: async () => {
try {
const resp: GetDraftBotDisplayInfoResponse =
await SpaceApi.GetDraftBotDisplayInfo({
bot_id: useBotInfoStore.getState().botId,
});
const botSkillBlockCollapsibleState =
resp.data?.tab_display_info ??
DEFAULT_BOT_SKILL_BLOCK_COLLAPSIBLE_STATE();
set(prevState => ({
...prevState,
botSkillBlockCollapsibleState,
}));
} catch (error) {
set(prevState => ({
...prevState,
botSkillBlockCollapsibleState:
DEFAULT_BOT_SKILL_BLOCK_COLLAPSIBLE_STATE(),
}));
throw error;
}
},
/**
* 存储用户主动展开/收起bot能力模块的状态
* ⚠️ 仅限主动操作时记录
* @see
*/
setBotSkillBlockCollapsibleState: (
$params: TabDisplayItems,
disableUpdateService?: boolean,
) => {
if (size($params) > 0) {
// 记录到本地状态机
set({
...get(),
botSkillBlockCollapsibleState: {
...get().botSkillBlockCollapsibleState,
...$params,
},
});
if (disableUpdateService) {
return;
}
// 同步到服务端
DeveloperApi.UpdateDraftBotDisplayInfo({
bot_id: useBotInfoStore.getState().botId,
display_info: { tab_display_info: $params },
space_id: useBotInfoStore.getState().space_id,
});
}
},
getIsPreview: version => typeof version !== 'undefined',
initStore: info => {
const { getIsPreview } = get();
set({
init: true,
isPreview: getIsPreview(info?.customVersion),
editable: info?.editable,
savingInfo: { saving: false, time: dayjs().format('HH:mm:ss') },
hasUnpublishChange: Boolean(info.has_unpublished_change),
});
},
clear: () => {
set({ ...getDefaultPageRuntimeStore() });
},
})),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botDetail.pageRuntime',
},
),
);

View File

@@ -0,0 +1,103 @@
/*
* 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 { devtools, subscribeWithSelector } from 'zustand/middleware';
import { create } from 'zustand';
import { produce } from 'immer';
import {
type BotInfoForUpdate,
type GetDraftBotInfoAgwData,
} from '@coze-arch/bot-api/playground_api';
import { type BotPrompt, PromptType } from '@coze-arch/bot-api/developer_api';
import {
type SetterAction,
setterActionFactory,
} from '../utils/setter-factory';
export const getDefaultPersonaStore = (): PersonaStore => ({
systemMessage: {
data: '',
prompt_type: PromptType.SYSTEM,
isOptimize: false,
record_id: '',
},
optimizePrompt: '',
promptOptimizeUuid: '',
promptOptimizeStatus: 'waitForRespond',
});
export interface RequiredBotPrompt extends BotPrompt {
prompt_type: PromptType;
data: string;
isOptimize: boolean;
record_id?: string;
}
/** Persona & Prompt 区域 */
export interface PersonaStore {
systemMessage: RequiredBotPrompt;
optimizePrompt: string;
promptOptimizeUuid: string;
promptOptimizeStatus: 'responding' | 'waitForRespond' | 'endResponse';
}
export interface PersonaAction {
setPersona: SetterAction<PersonaStore>;
setPersonaByImmer: (update: (state: PersonaStore) => void) => void;
transformDto2Vo: (data: GetDraftBotInfoAgwData) => RequiredBotPrompt;
transformVo2Dto: (
persona: Partial<RequiredBotPrompt>,
) => BotInfoForUpdate['prompt_info'];
initStore: (botData: GetDraftBotInfoAgwData) => void;
clear: () => void;
}
export const usePersonaStore = create<PersonaStore & PersonaAction>()(
devtools(
subscribeWithSelector((set, get) => ({
...getDefaultPersonaStore(),
setPersona: setterActionFactory<PersonaStore>(set),
setPersonaByImmer: update =>
set(produce<PersonaStore>(persona => update(persona))),
transformDto2Vo: botData =>
({
data: botData.bot_info?.prompt_info?.prompt ?? '',
prompt_type: PromptType.SYSTEM,
isOptimize: false,
record_id: '',
}) as unknown as RequiredBotPrompt,
transformVo2Dto: persona =>
({
prompt: persona?.data || '',
}) as unknown as BotInfoForUpdate['prompt_info'],
initStore: botData => {
const { setPersonaByImmer, transformDto2Vo } = get();
botData &&
setPersonaByImmer(store => {
store.systemMessage = transformDto2Vo(botData);
});
},
clear: () => {
set({ ...getDefaultPersonaStore() });
},
})),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botDetail.persona',
},
),
);

View File

@@ -0,0 +1,77 @@
/*
* 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 { devtools, subscribeWithSelector } from 'zustand/middleware';
import { create } from 'zustand';
import {
type BotInfoForUpdate,
type GetDraftBotInfoAgwData,
type UserQueryCollectConf,
} from '@coze-arch/idl/playground_api';
import {
type SetterAction,
setterActionFactory,
} from '../utils/setter-factory';
export interface QueryCollectStore {
is_collected: boolean;
private_policy: string;
}
export const getDefaultQueryCollectStore = (): QueryCollectStore => ({
is_collected: false,
private_policy: '',
});
export interface QueryCollectAction {
setQueryCollect: SetterAction<QueryCollectStore>;
transformDto2Vo: (data: GetDraftBotInfoAgwData) => UserQueryCollectConf;
transformVo2Dto: (
queryCollectConf: UserQueryCollectConf,
) => BotInfoForUpdate['user_query_collect_conf'];
initStore: (data: GetDraftBotInfoAgwData) => void;
clear: () => void;
}
export const useQueryCollectStore = create<
QueryCollectStore & QueryCollectAction
>()(
devtools(
subscribeWithSelector((set, get) => ({
...getDefaultQueryCollectStore(),
setQueryCollect: setterActionFactory<QueryCollectStore>(set),
transformDto2Vo: botData => {
const data = botData.bot_info?.user_query_collect_conf;
return {
is_collected: data?.is_collected,
private_policy: data?.private_policy,
};
},
transformVo2Dto: info => info,
initStore: botData => {
const { transformDto2Vo } = get();
set(transformDto2Vo(botData));
},
clear: () => {
set({ ...getDefaultQueryCollectStore() });
},
})),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botDetail.queryCollect',
},
),
);