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,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types='@coze-arch/bot-typings' />

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useShallow } from 'zustand/react/shallow';
import { getBotDetailIsReadonlyByState } from '../utils/get-read-only';
import { usePageRuntimeStore } from '../store/page-runtime';
import { useCollaborationStore } from '../store/collaboration';
export const useBotDetailIsReadonly = (): boolean => {
const { editable, isPreview } = usePageRuntimeStore(
useShallow(state => ({
editable: state.editable,
isPreview: state.isPreview,
})),
);
const editLockStatus = useCollaborationStore(state => state.editLockStatus);
return getBotDetailIsReadonlyByState({
editable,
isPreview,
editLockStatus,
});
};

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useBotSkillStore } from '../store/bot-skill';
export const useChatBackgroundState = () => {
const backgroundState = useBotSkillStore(s => s.backgroundImageInfoList);
const showBackground =
!!backgroundState?.[0]?.mobile_background_image?.origin_image_url;
const mobileBackGround =
backgroundState?.[0]?.web_background_image?.origin_image_url;
const pcBackground =
backgroundState?.[0]?.web_background_image?.origin_image_url;
return {
showBackground,
mobileBackGround,
pcBackground,
backgroundModeClassName: showBackground ? '!coz-fg-images-white' : '',
};
};

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { avatarBackgroundWebSocket } from './utils/avatar-background-socket';
export { useBotDetailIsReadonly } from './hooks/use-bot-detail-readonly';
export {
TTSInfo,
type VariableItem,
VariableKeyErrType,
type TableMemoryItem,
type SuggestQuestionMessage,
type BotDetailSkill,
type WorkFlowItemType,
type DatabaseInfo,
type DatabaseList,
type KnowledgeConfig,
type TagListType,
type ExtendOnboardingContent,
TimeCapsuleOptionsEnum,
} from './types/skill';
export { updateHeaderStatus } from './utils/handle-status';
export { initBotDetailStore } from './init/init-bot-detail-store';
export { useBotDetailStoreSet } from './store/index';
export {
autosaveManager,
personaSaveManager,
botSkillSaveManager,
multiAgentSaveManager,
registerMultiAgentConfig,
getBotDetailDtoInfo,
saveConnectorType,
saveDeleteAgents,
saveUpdateAgents,
saveMultiAgentData,
saveFileboxMode,
saveTableMemory,
saveTTSConfig,
saveTimeCapsule,
saveDevHooksConfig,
updateShortcutSort,
updateBotRequest,
} from './save-manager';
export { getBotDetailIsReadonly } from './utils/get-read-only';
export { uniqMemoryList } from './utils/uniq-memory-list';
export { verifyBracesAndToast } from './utils/submit';
export { storage } from './utils/storage';
export { findTargetAgent, findFirstAgentId } from './utils/find-agent';
export { manuallySwitchAgent, deleteAgent } from './utils/handle-agent';
export { type Agent, type BotMultiAgent, type DraftBotVo } from './types/agent';
export { getReplacedBotPrompt } from './utils/save';
export { getExecuteDraftBotRequestId } from './utils/execute-draft-bot-request-id';
export { useManuallySwitchAgentStore } from './store/manually-switch-agent-store';
export { useChatBackgroundState } from './hooks/use-chat-background-state';
export {
DotStatus,
GenerateAvatarModal,
GenerateType,
} from './types/generate-image';
export { useGenerateImageStore } from './store/generate-image-store';
export { initGenerateImageStore } from './init/init-generate-image';
export { useMonetizeConfigStore } from './store/monetize-config-store';

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
REPORT_EVENTS as ReportEventNames,
createReportEvent,
} from '@coze-arch/report-events';
import { type BotMonetizationConfigData } from '@coze-arch/idl/benefit';
import { type GetDraftBotInfoAgwData } from '@coze-arch/bot-api/playground_api';
import { type HistoryInfo } from '@coze-arch/bot-api/developer_api';
import { useQueryCollectStore } from '../store/query-collect';
import { usePersonaStore } from '../store/persona';
import { usePageRuntimeStore } from '../store/page-runtime';
import { useMultiAgentStore } from '../store/multi-agent';
import { useMonetizeConfigStore } from '../store/monetize-config-store';
import { useModelStore } from '../store/model';
import { useBotDetailStoreSet } from '../store/index';
import { useCollaborationStore } from '../store/collaboration';
import { useBotSkillStore } from '../store/bot-skill';
import { useBotInfoStore } from '../store/bot-info';
import { useAuditInfoStore } from '../store/audit-info';
import { getBotDataService } from '../services/get-bot-data-service';
export async function initBotDetailStore(params?: {
version?: HistoryInfo['version'];
scene?: 'bot' | 'market';
}) {
const { version, scene = 'bot' } = params ?? {};
const getRecordEvent = createReportEvent({
eventName: ReportEventNames.botDebugGetRecord,
});
const { botId, version: botInfoVersion } = useBotInfoStore.getState();
const { setPageRuntimeBotInfo } = usePageRuntimeStore.getState();
const { clear } = useBotDetailStoreSet;
try {
setPageRuntimeBotInfo({ init: false });
const getBotInfoEvent = createReportEvent({
eventName: ReportEventNames.botGetDraftBotInfo,
});
try {
const { botData, monetizeConfig = {} } = await getBotDataService({
scene,
botId,
customVersion: version,
botInfoVersion,
});
// 处理bot草稿页特有字段
if (scene === 'bot') {
initBotSceneStore(botData, version);
}
// 初始化store set
initBotDetailStoreSet(botData, monetizeConfig);
getBotInfoEvent.success();
} catch (e) {
clear();
getBotInfoEvent.error({
reason: 'get new draft bot info fail',
error: e instanceof Error ? e : void 0,
});
throw e;
}
getRecordEvent.success();
} catch (e) {
getRecordEvent.error({
reason: 'init fail',
error: e instanceof Error ? e : void 0,
});
throw e;
}
}
const initBotSceneStore = (info: GetDraftBotInfoAgwData, version?: string) => {
const { initStore: initPageRuntimeStore } = usePageRuntimeStore.getState();
const { initStore: initCollaborationStore } =
useCollaborationStore.getState();
initPageRuntimeStore({
...info,
customVersion: version,
});
initCollaborationStore(info);
};
const initBotDetailStoreSet = (
botData: GetDraftBotInfoAgwData,
monetizeConfig: BotMonetizationConfigData,
) => {
const { initStore: initBotInfoStore } = useBotInfoStore.getState();
const { initStore: initPersonaStore } = usePersonaStore.getState();
const { initStore: initModelStore } = useModelStore.getState();
const { initStore: initBotSkillStore } = useBotSkillStore.getState();
const { initStore: initMultiAgentStore } = useMultiAgentStore.getState();
const { initStore: initMonetizeConfigStore } =
useMonetizeConfigStore.getState();
const { initStore: initQueryCollectStore } = useQueryCollectStore.getState();
const { initStore: initAuditInfoStore } = useAuditInfoStore.getState();
initBotInfoStore(botData);
initPersonaStore(botData);
initModelStore(botData);
initBotSkillStore(botData);
initMultiAgentStore(botData);
// 设置信息付费信息
initMonetizeConfigStore(monetizeConfig);
initQueryCollectStore(botData);
initAuditInfoStore(botData);
};

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { logger } from '@coze-arch/logger';
import { getFlags } from '@coze-arch/bot-flags';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { getBotDetailIsReadonly } from '../utils/get-read-only';
import {
getInitAvatarInfo,
getInitBackgroundInfo,
} from '../utils/generate-image';
import { initAvatarBackgroundWebSocket } from '../utils/avatar-background-socket';
import { useGenerateImageStore } from '../store/generate-image-store';
import { useBotInfoStore } from '../store/bot-info';
export const initGenerateImageStore = async () => {
try {
const {
updateImageList,
updateNoticeList,
setGenerateAvatarModalByImmer,
setGenerateBackgroundModalByImmer,
clearGenerateImageStore,
} = useGenerateImageStore.getState();
const { botId } = useBotInfoStore.getState();
const isReadOnly = getBotDetailIsReadonly();
const FLAGS = getFlags();
if (isReadOnly || !FLAGS['bot.studio.gif_avater_background']) {
return;
}
// 初始化一下,防止从创建页跳到编辑页把创建页的状态带过来
clearGenerateImageStore();
const resp = await PlaygroundApi.GetPicTask({
bot_id: botId,
});
const respData = resp?.data ?? {};
const { tasks = [], notices = [] } = respData;
updateImageList(tasks);
updateNoticeList(notices);
setGenerateAvatarModalByImmer(state => {
getInitAvatarInfo(respData, state);
});
setGenerateBackgroundModalByImmer(state => {
getInitBackgroundInfo(respData, state);
});
initAvatarBackgroundWebSocket();
} catch (error) {
const e = error instanceof Error ? error : new Error(error as string);
logger.error({ error: e });
}
};

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type BackgroundImageInfo } from '@coze-arch/bot-api/developer_api';
import { DebounceTime, type HostedObserverConfig } from '@coze-studio/autosave';
import type { BotSkillStore } from '@/store/bot-skill';
import { ItemTypeExtra } from '@/save-manager/types';
type RegisterChatBackgroundConfig = HostedObserverConfig<
BotSkillStore,
ItemTypeExtra,
BackgroundImageInfo[]
>;
export const chatBackgroundConfig: RegisterChatBackgroundConfig = {
key: ItemTypeExtra.ChatBackGround,
selector: store => store.backgroundImageInfoList,
debounce: DebounceTime.Immediate,
middleware: {
onBeforeSave: dataSource => ({
background_image_info_list: dataSource,
}),
},
};

View File

@@ -0,0 +1,48 @@
/*
* 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 HostedObserverConfig } from '@coze-studio/autosave';
import { type BotSkillStore } from '@/store/bot-skill';
import { type BizKey, type ScopeStateType } from '@/save-manager/types';
import { workflowsConfig } from './workflows';
import { voicesInfoConfig } from './voices-info';
import { variablesConfig } from './variables';
import { taskInfoConfig } from './task-info';
import { suggestionConfig } from './suggestion-config';
import { pluginConfig } from './plugin';
import { onboardingConfig } from './onboarding-content';
import { layoutInfoConfig } from './layout-info';
import { knowledgeConfig } from './knowledge';
import { chatBackgroundConfig } from './chat-background';
export const registers: HostedObserverConfig<
BotSkillStore,
BizKey,
ScopeStateType
>[] = [
pluginConfig,
chatBackgroundConfig,
onboardingConfig,
knowledgeConfig,
layoutInfoConfig,
suggestionConfig,
taskInfoConfig,
variablesConfig,
workflowsConfig,
voicesInfoConfig,
];

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DebounceTime, type HostedObserverConfig } from '@coze-studio/autosave';
import type { KnowledgeConfig } from '@/types/skill';
import { type BotSkillStore, useBotSkillStore } from '@/store/bot-skill';
import { ItemType } from '@/save-manager/types';
type RegisterKnowledge = HostedObserverConfig<
BotSkillStore,
ItemType,
KnowledgeConfig
>;
export const knowledgeConfig: RegisterKnowledge = {
key: ItemType.DataSet,
selector: store => store.knowledge,
debounce: {
default: DebounceTime.Immediate,
'dataSetInfo.min_score': DebounceTime.Medium,
'dataSetInfo.top_k': DebounceTime.Medium,
},
middleware: {
onBeforeSave: dataSource => ({
knowledge: useBotSkillStore
.getState()
.transformVo2Dto.knowledge(dataSource),
}),
},
};

View File

@@ -0,0 +1,40 @@
/*
* 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 LayoutInfo } from '@coze-arch/idl/developer_api';
import { DebounceTime, type HostedObserverConfig } from '@coze-studio/autosave';
import { type BotSkillStore, useBotSkillStore } from '@/store/bot-skill';
import { ItemTypeExtra } from '@/save-manager/types';
type RegisterLayoutInfo = HostedObserverConfig<
BotSkillStore,
ItemTypeExtra,
LayoutInfo
>;
export const layoutInfoConfig: RegisterLayoutInfo = {
key: ItemTypeExtra.LayoutInfo,
selector: store => store.layoutInfo,
debounce: DebounceTime.Immediate,
middleware: {
onBeforeSave: layoutInfo => ({
layout_info: useBotSkillStore
.getState()
.transformVo2Dto.layoutInfo(layoutInfo),
}),
},
};

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type OnboardingInfo } from '@coze-arch/bot-api/playground_api';
import { DebounceTime, type HostedObserverConfig } from '@coze-studio/autosave';
import type { ExtendOnboardingContent } from '@/types/skill';
import { useBotSkillStore } from '@/store/bot-skill';
import type { BotSkillStore } from '@/store/bot-skill';
import { ItemType } from '@/save-manager/types';
type RegisterOnboardingContent = HostedObserverConfig<
BotSkillStore,
ItemType,
ExtendOnboardingContent
>;
export const onboardingConfig: RegisterOnboardingContent = {
key: ItemType.ONBOARDING,
selector: {
deps: [state => state.onboardingContent],
transformer: onboardingContent =>
useBotSkillStore.getState().transformVo2Dto.onboarding(onboardingContent),
},
debounce: {
default: DebounceTime.Immediate,
prologue: DebounceTime.Long,
suggested_questions: {
arrayType: true,
action: {
N: DebounceTime.Immediate,
D: DebounceTime.Immediate,
E: DebounceTime.Long,
},
},
},
middleware: {
onBeforeSave: (dataSource: OnboardingInfo) => ({
onboarding_info: dataSource,
}),
},
};

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { cloneDeep } from 'lodash-es';
import { DebounceTime, type HostedObserverConfig } from '@coze-studio/autosave';
import type { EnabledPluginApi } from '@/types/skill';
import { type BotSkillStore, useBotSkillStore } from '@/store/bot-skill';
import { ItemType } from '@/save-manager/types';
type RegisterSystemContent = HostedObserverConfig<
BotSkillStore,
ItemType,
EnabledPluginApi[]
>;
export const pluginConfig: RegisterSystemContent = {
key: ItemType.APIINFO,
selector: store => store.pluginApis,
debounce: DebounceTime.Immediate,
middleware: {
onBeforeSave: dataSource => {
// 必须先深克隆,处理原数据会改动 store 的值
const clonePluginApis = cloneDeep(dataSource);
const newPluginApis = clonePluginApis.map(item => {
// ai生成动画仅生效一次请求接口时删除
delete item.autoAddCss;
return item;
});
return {
plugin_info_list: useBotSkillStore
.getState()
.transformVo2Dto.plugin(newPluginApis),
};
},
},
};

View File

@@ -0,0 +1,43 @@
/*
* 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 { DebounceTime, type HostedObserverConfig } from '@coze-studio/autosave';
import type { BotSuggestionConfig } from '@/types/skill';
import { type BotSkillStore, useBotSkillStore } from '@/store/bot-skill';
import { ItemType } from '@/save-manager/types';
type RegisterSuggestionConfig = HostedObserverConfig<
BotSkillStore,
ItemType,
BotSuggestionConfig
>;
export const suggestionConfig: RegisterSuggestionConfig = {
key: ItemType.SUGGESTREPLY,
selector: store => store.suggestionConfig,
debounce: {
default: DebounceTime.Immediate,
customized_suggest_prompt: DebounceTime.Long,
},
middleware: {
onBeforeSave: dataSource => ({
suggest_reply_info: useBotSkillStore
.getState()
.transformVo2Dto.suggestionConfig(dataSource),
}),
},
};

View File

@@ -0,0 +1,35 @@
/*
* 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 { DebounceTime, type HostedObserverConfig } from '@coze-studio/autosave';
import { type BotSkillStore, useBotSkillStore } from '@/store/bot-skill';
import { ItemType } from '@/save-manager/types';
type RegisterTaskInfo = HostedObserverConfig<BotSkillStore, ItemType, boolean>;
export const taskInfoConfig: RegisterTaskInfo = {
key: ItemType.TASK,
selector: store => store.taskInfo.user_task_allowed,
debounce: DebounceTime.Immediate,
middleware: {
onBeforeSave: dataSource => ({
task_info: useBotSkillStore.getState().transformVo2Dto.task({
user_task_allowed: dataSource,
}),
}),
},
};

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { REPORT_EVENTS as ReportEventNames } from '@coze-arch/report-events';
import { DebounceTime, type HostedObserverConfig } from '@coze-studio/autosave';
import { CustomError } from '@coze-arch/bot-error';
import { uniqMemoryList } from '@/utils/uniq-memory-list';
import { type VariableItem, VariableKeyErrType } from '@/types/skill';
import { usePageRuntimeStore } from '@/store/page-runtime';
import { type BotSkillStore, useBotSkillStore } from '@/store/bot-skill';
import { ItemType } from '@/save-manager/types';
type RegisterVariables = HostedObserverConfig<
BotSkillStore,
ItemType,
VariableItem[]
>;
export const variablesConfig: RegisterVariables = {
key: ItemType.PROFILEMEMORY,
selector: store => store.variables,
debounce: DebounceTime.Immediate,
middleware: {
onBeforeSave: dataSource => {
const { editable } = usePageRuntimeStore.getState();
const filteredVariables = uniqMemoryList(dataSource).filter(i => {
const errType = i?.errType || VariableKeyErrType.KEY_CHECK_PASS;
return errType > VariableKeyErrType.KEY_CHECK_PASS;
});
if (!filteredVariables.length && editable) {
return {
variable_list: useBotSkillStore
.getState()
.transformVo2Dto.variables(dataSource),
};
}
throw new CustomError(
ReportEventNames.parmasValidation,
'botSkill.variables return nothing',
);
},
},
};

View File

@@ -0,0 +1,49 @@
/*
* 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 { merge } from 'lodash-es';
import { type BotInfoForUpdate } from '@coze-arch/idl/playground_api';
import { DebounceTime, type HostedObserverConfig } from '@coze-studio/autosave';
import { type TTSInfo, type VoicesInfo } from '@/types/skill';
import { transformVo2Dto } from '@/store/bot-skill/transform';
import { type BotSkillStore } from '@/store/bot-skill';
import { ItemType } from '@/save-manager/types';
interface Values {
voicesInfo: VoicesInfo;
tts: TTSInfo;
}
type RegisterVariables = HostedObserverConfig<BotSkillStore, ItemType, Values>;
export const voicesInfoConfig: RegisterVariables = {
key: ItemType.PROFILEMEMORY,
selector: store => ({ voicesInfo: store.voicesInfo, tts: store.tts }),
debounce: DebounceTime.Immediate,
middleware: {
// ! any warning 改动的时候要仔细
onBeforeSave: (
values: Values,
): Pick<Required<BotInfoForUpdate>, 'voices_info'> => ({
voices_info: merge(
{},
transformVo2Dto.tts(values.tts),
transformVo2Dto.voicesInfo(values.voicesInfo),
),
}),
},
};

View File

@@ -0,0 +1,50 @@
/*
* 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 { cloneDeep, uniqBy } from 'lodash-es';
import { DebounceTime, type HostedObserverConfig } from '@coze-studio/autosave';
import type { WorkFlowItemType } from '@/types/skill';
import { type BotSkillStore, useBotSkillStore } from '@/store/bot-skill';
import { ItemType } from '@/save-manager/types';
type RegisterWorkflows = HostedObserverConfig<
BotSkillStore,
ItemType,
WorkFlowItemType[]
>;
export const workflowsConfig: RegisterWorkflows = {
key: ItemType.WORKFLOW,
selector: store => store.workflows,
debounce: DebounceTime.Immediate,
middleware: {
onBeforeSave: (dataSource: WorkFlowItemType[]) => {
const workflowsToBackend = cloneDeep(dataSource);
const filterList = uniqBy(workflowsToBackend, 'workflow_id').map(v => {
// 解决加载图标的时候由于图标链接失效而报错不在这里保存会失效的workflow的plugin_icon而是每次都拉取最新的有效的图标链接
v.plugin_icon = '';
return v;
});
return {
workflow_info_list: useBotSkillStore
.getState()
.transformVo2Dto.workflow(filterList),
};
},
},
};

View File

@@ -0,0 +1,33 @@
/*
* 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 { AutosaveManager } from '@coze-studio/autosave';
import { useBotSkillStore, type BotSkillStore } from '@/store/bot-skill';
import { type BizKey, type ScopeStateType } from '@/save-manager/types';
import { saveRequest } from '../request';
import { registers } from './configs';
export const botSkillSaveManager = new AutosaveManager<
BotSkillStore,
BizKey,
ScopeStateType
>({
store: useBotSkillStore,
registers,
saveRequest,
});

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { personaSaveManager } from './persona';
import { modelSaveManager } from './model';
import { botSkillSaveManager } from './bot-skill';
const managers = [personaSaveManager, botSkillSaveManager, modelSaveManager];
export const autosaveManager = {
start: () => {
console.log('start:>>');
managers.forEach(manager => {
manager.start();
});
},
close: () => {
console.log('close:>>');
managers.forEach(manager => {
manager.close();
});
},
};

View File

@@ -0,0 +1,43 @@
/*
* 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 ModelInfo } from '@coze-arch/bot-api/developer_api';
import { DebounceTime, type HostedObserverConfig } from '@coze-studio/autosave';
import { type ModelStore, useModelStore } from '@/store/model';
import { ItemType } from '@/save-manager/types';
type RegisterSystemContent = HostedObserverConfig<
ModelStore,
ItemType,
ModelInfo
>;
export const modelConfig: RegisterSystemContent = {
key: ItemType.OTHERINFO,
selector: store => store.config,
debounce: {
default: DebounceTime.Immediate,
temperature: DebounceTime.Medium,
max_tokens: DebounceTime.Medium,
'ShortMemPolicy.HistoryRound': DebounceTime.Medium,
},
middleware: {
onBeforeSave: dataSource => ({
model_info: useModelStore.getState().transformVo2Dto(dataSource),
}),
},
};

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 ModelInfo } from '@coze-arch/bot-api/developer_api';
import { AutosaveManager } from '@coze-studio/autosave';
import { useModelStore, type ModelStore } from '@/store/model';
import { type BizKey } from '@/save-manager/types';
import { saveRequest } from '../request';
import { modelConfig } from './config';
export const modelSaveManager = new AutosaveManager<
ModelStore,
BizKey,
ModelInfo
>({
store: useModelStore,
registers: [modelConfig],
saveRequest,
});

View File

@@ -0,0 +1,49 @@
/*
* 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 { DebounceTime, type HostedObserverConfig } from '@coze-studio/autosave';
import { ItemTypeExtra } from '../../types';
import type { Agent } from '../../../types/agent';
import { type MultiAgentStore } from '../../../store/multi-agent/store';
type RegisterSystemContent = HostedObserverConfig<
MultiAgentStore,
ItemTypeExtra,
Agent
>;
export const registerMultiAgentConfig: RegisterSystemContent = {
key: ItemTypeExtra.MultiAgent,
selector: state => state.agents?.[0],
debounce: {
default: DebounceTime.Immediate,
description: DebounceTime.Long,
'position.x': DebounceTime.Medium,
'position.y': DebounceTime.Medium,
'skills.knowledge.dataSetInfo.min_score': DebounceTime.Medium,
'skills.knowledge.dataSetInfo.top_k': DebounceTime.Medium,
'skills.knowledge.dataSetInfo.no_recall_reply_customize_prompt':
DebounceTime.Long,
'model.temperature': DebounceTime.Medium,
'model.max_tokens': DebounceTime.Medium,
'model.top_p': DebounceTime.Medium,
'model.ShortMemPolicy.HistoryRound': DebounceTime.Medium,
prompt: DebounceTime.Long, // agent 提示词
'suggestion.customized_suggest_prompt': DebounceTime.Long,
intents: { arrayType: true, action: { E: DebounceTime.Long } },
},
};

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { AutosaveManager, type SaveRequest } from '@coze-studio/autosave';
import { storage } from '@/utils/storage';
import type { Agent } from '@/types/agent';
import {
type MultiAgentStore,
useMultiAgentStore,
} from '@/store/multi-agent/store';
import { useBotInfoStore } from '@/store/bot-info';
import { saveFetcher } from '../../utils/save-fetcher';
import { ItemTypeExtra } from '../../types';
import { registerMultiAgentConfig } from './config';
const saveRequestAgent: SaveRequest<Agent, ItemTypeExtra> = async (
payload: Agent,
) =>
await saveFetcher(() => {
const params = useMultiAgentStore.getState().transformVo2Dto.agent(payload);
return PlaygroundApi.UpdateAgentV2({
...params,
id: payload.id,
bot_id: useBotInfoStore.getState().botId,
space_id: useSpaceStore.getState().getSpaceId(),
base_commit_version: storage.baseVersion,
});
}, ItemTypeExtra.MultiAgent);
export const multiAgentSaveManager = new AutosaveManager<
MultiAgentStore,
ItemTypeExtra,
Agent
>({
store: useMultiAgentStore,
registers: [registerMultiAgentConfig],
saveRequest: saveRequestAgent,
});

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DebounceTime, type HostedObserverConfig } from '@coze-studio/autosave';
import {
usePersonaStore,
type PersonaStore,
type RequiredBotPrompt,
} from '@/store/persona';
import { ItemType } from '@/save-manager/types';
type RegisterSystemContent = HostedObserverConfig<
PersonaStore,
ItemType,
RequiredBotPrompt
>;
export const personaConfig: RegisterSystemContent = {
key: ItemType.SYSTEMINFO,
selector: state => state.systemMessage,
debounce: () => {
const { systemMessage } = usePersonaStore.getState();
const { isOptimize } = systemMessage;
console.log('systemMessage:>>', systemMessage);
console.log('isOptimize:>>', isOptimize);
if (isOptimize) {
return DebounceTime.Immediate;
}
return DebounceTime.Long;
},
middleware: {
onBeforeSave: nextState => ({
prompt_info: usePersonaStore.getState().transformVo2Dto(nextState),
}),
},
};

View File

@@ -0,0 +1,37 @@
/*
* 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 { AutosaveManager } from '@coze-studio/autosave';
import {
usePersonaStore,
type PersonaStore,
type RequiredBotPrompt,
} from '@/store/persona';
import { type ItemType } from '@/save-manager/types';
import { saveRequest } from '../request';
import { personaConfig } from './config';
export const personaSaveManager = new AutosaveManager<
PersonaStore,
ItemType,
RequiredBotPrompt
>({
store: usePersonaStore,
registers: [personaConfig],
saveRequest,
});

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PlaygroundApi } from '@coze-arch/bot-api';
import { type SaveRequest } from '@coze-studio/autosave';
import { storage } from '@/utils/storage';
import { useBotInfoStore } from '@/store/bot-info';
import { type BizKey, type ScopeStateType } from '@/save-manager/types';
import { saveFetcher } from '../utils/save-fetcher';
/**
* 自动保存统一请求方法
*/
export const saveRequest: SaveRequest<ScopeStateType, BizKey> = async (
payload: ScopeStateType,
itemType: BizKey,
) => {
const { botId } = useBotInfoStore.getState();
await saveFetcher(
async () =>
await PlaygroundApi.UpdateDraftBotInfoAgw({
bot_info: {
bot_id: botId,
...payload,
},
base_commit_version: storage.baseVersion,
}),
itemType,
);
};

View File

@@ -0,0 +1,39 @@
/*
* 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 { autosaveManager } from './auto-save/index';
export { personaSaveManager } from './auto-save/persona';
export { botSkillSaveManager } from './auto-save/bot-skill';
export { multiAgentSaveManager } from './auto-save/multi-agent';
export { modelSaveManager } from './auto-save/model';
export { registerMultiAgentConfig } from './auto-save/multi-agent/config';
export { updateBotRequest } from './utils/save-fetcher';
export { saveFileboxMode } from './manual-save/filebox';
export {
saveConnectorType,
saveDeleteAgents,
saveUpdateAgents,
saveMultiAgentData,
} from './manual-save/multi-agent';
export { saveTableMemory } from './manual-save/memory-table';
export { saveTTSConfig } from './manual-save/tts';
export { saveDevHooksConfig } from './manual-save/dev-hooks';
export { updateShortcutSort } from './manual-save/shortcuts';
export { updateQueryCollect } from './manual-save/query-collect';
export { saveTimeCapsule } from './manual-save/time-capsule';
export { getBotDetailDtoInfo } from './utils/bot-dto-info';
export { ItemTypeExtra } from './types';

View File

@@ -0,0 +1,29 @@
/*
* 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 HookInfo } from '@coze-arch/idl/playground_api';
import { ItemType } from '@coze-arch/bot-api/developer_api';
import { saveFetcher, updateBotRequest } from '../utils/save-fetcher';
export const saveDevHooksConfig = async (hooksInfo: HookInfo) =>
saveFetcher(
() =>
updateBotRequest({
hook_info: hooksInfo,
}),
ItemType.HOOKINFO,
);

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type FileboxInfoMode,
ItemType,
} from '@coze-arch/bot-api/developer_api';
import { useBotSkillStore } from '@/store/bot-skill';
import { saveFetcher, updateBotRequest } from '../utils/save-fetcher';
export const saveFileboxMode = async (nextMode: FileboxInfoMode) => {
const { filebox: fileboxConfig } = useBotSkillStore.getState();
return await saveFetcher(
() =>
updateBotRequest({
filebox_info: useBotSkillStore
.getState()
.transformVo2Dto.filebox(
fileboxConfig?.mode ? fileboxConfig : { mode: nextMode },
),
}),
ItemType.TABLE,
);
};

View File

@@ -0,0 +1,35 @@
/*
* 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 { ItemType } from '@coze-arch/bot-api/developer_api';
import { useBotSkillStore } from '@/store/bot-skill';
import { saveFetcher, updateBotRequest } from '../utils/save-fetcher';
export async function saveTableMemory() {
const { databaseList } = useBotSkillStore.getState();
return await saveFetcher(
() =>
updateBotRequest({
database_list: useBotSkillStore
.getState()
.transformVo2Dto.databaseList(databaseList),
}),
ItemType.TABLE,
);
}

View File

@@ -0,0 +1,96 @@
/*
* 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 { useSpaceStore } from '@coze-arch/bot-studio-store';
import { type MultiAgentConnectorType } from '@coze-arch/bot-api/playground_api';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { type LineType } from '@flowgram-adapter/free-layout-editor';
import { storage } from '@/utils/storage';
import type { Agent } from '@/types/agent';
import { useMultiAgentStore } from '@/store/multi-agent';
import { useBotInfoStore } from '@/store/bot-info';
import { saveFetcher } from '../utils/save-fetcher';
import { ItemTypeExtra } from '../types';
// skill结构化的接口
export async function saveUpdateAgents(agent: Agent) {
return await saveFetcher(
() =>
PlaygroundApi.UpdateAgentV2({
...useMultiAgentStore.getState().transformVo2Dto.agent(agent),
bot_id: useBotInfoStore.getState().botId,
space_id: useSpaceStore.getState().getSpaceId(),
base_commit_version: storage.baseVersion,
}),
ItemTypeExtra.MultiAgent,
);
}
export async function saveDeleteAgents(deleteAgentId: string) {
return await saveFetcher(
() =>
PlaygroundApi.UpdateAgentV2({
bot_id: useBotInfoStore.getState().botId,
space_id: useSpaceStore.getState().getSpaceId(),
id: deleteAgentId,
is_delete: true,
base_commit_version: storage.baseVersion,
}),
ItemTypeExtra.MultiAgent,
);
}
export function saveDeleteAgentsV3(deleteAgentId: string) {
return saveFetcher(
() =>
PlaygroundApi.UpdateAgentV2({
bot_id: useBotInfoStore.getState().botId,
space_id: useSpaceStore.getState().getSpaceId(),
id: deleteAgentId,
is_delete: true,
base_commit_version: storage.baseVersion,
}),
ItemTypeExtra.MultiAgent,
);
}
export async function saveMultiAgentData() {
return await saveFetcher(
() =>
PlaygroundApi.UpdateMultiAgent({
space_id: useSpaceStore.getState().getSpaceId(),
bot_id: useBotInfoStore.getState().botId,
session_type: useMultiAgentStore.getState().chatModeConfig.type,
base_commit_version: storage.baseVersion,
}),
ItemTypeExtra.MultiAgent,
);
}
export async function saveConnectorType(connectorType: LineType) {
return await saveFetcher(
() =>
PlaygroundApi.UpdateMultiAgent({
space_id: useSpaceStore.getState().getSpaceId(),
bot_id: useBotInfoStore.getState().botId,
connector_type: connectorType as unknown as MultiAgentConnectorType,
base_commit_version: storage.baseVersion,
}),
ItemTypeExtra.ConnectorType,
);
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type UpdateDraftBotInfoAgwResponse } from '@coze-arch/idl/playground_api';
import { type UserQueryCollectConf } from '@coze-arch/bot-api/developer_api';
import { saveFetcher, updateBotRequest } from '../utils/save-fetcher';
import { ItemTypeExtra } from '../types';
export const updateQueryCollect = async (
queryCollectConf: UserQueryCollectConf,
) => {
// @ts-expect-error -- linter-disable-autofix
let updateResult: UpdateDraftBotInfoAgwResponse = null;
await saveFetcher(async () => {
const res = await updateBotRequest({
user_query_collect_conf: queryCollectConf,
});
updateResult = res;
return res;
}, ItemTypeExtra.QueryCollect);
return updateResult;
};

View File

@@ -0,0 +1,28 @@
/*
* 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 { saveFetcher, updateBotRequest } from '../utils/save-fetcher';
import { ItemTypeExtra } from '../types';
export const updateShortcutSort = async (shortcutSort: string[]) =>
await saveFetcher(
() =>
updateBotRequest({
shortcut_sort: shortcutSort,
}),
ItemTypeExtra.Shortcut,
);

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useBotSkillStore } from '@/store/bot-skill';
import { saveFetcher, updateBotRequest } from '../utils/save-fetcher';
import { ItemTypeExtra } from '../types';
export async function saveTimeCapsule() {
const { timeCapsule, transformVo2Dto } = useBotSkillStore.getState();
return await saveFetcher(
() =>
updateBotRequest({
bot_tag_info: transformVo2Dto.timeCapsule({
time_capsule_mode: timeCapsule.time_capsule_mode,
disable_prompt_calling: timeCapsule.disable_prompt_calling,
time_capsule_time_to_live: timeCapsule.time_capsule_time_to_live,
}),
}),
ItemTypeExtra.TimeCapsule,
);
}

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.
*/
import { cloneDeep, merge } from 'lodash-es';
import { useBotSkillStore } from '@/store/bot-skill';
import { saveFetcher, updateBotRequest } from '../utils/save-fetcher';
import { ItemTypeExtra } from '../types';
export const saveTTSConfig = async () => {
const { tts, transformVo2Dto, voicesInfo } = useBotSkillStore.getState();
const {
muted = false,
close_voice_call = false,
i18n_lang_voice = {},
autoplay = false,
autoplay_voice = {},
i18n_lang_voice_str,
} = tts;
const cloneVoiceInfo = {
muted,
close_voice_call,
i18n_lang_voice: cloneDeep(i18n_lang_voice),
autoplay,
autoplay_voice: cloneDeep(autoplay_voice),
i18n_lang_voice_str: cloneDeep(i18n_lang_voice_str),
};
return await saveFetcher(
() =>
updateBotRequest({
voices_info: merge(
{},
transformVo2Dto.tts(cloneVoiceInfo),
transformVo2Dto.voicesInfo(voicesInfo),
),
}),
ItemTypeExtra.TTS,
);
};

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ItemType } from '@coze-arch/bot-api/developer_api';
// 走自动保存update接口的scope服务端会维护ItemType其他scope前端维护在ItemTypeExtra中
export enum ItemTypeExtra {
MultiAgent = 1024,
TTS = 1025,
ConnectorType = 1026,
ChatBackGround = 1027,
Shortcut = 1028,
QueryCollect = 1029,
LayoutInfo = 1030,
TaskInfo = 1031,
TimeCapsule = 1032,
}
export type BizKey = ItemType | ItemTypeExtra | undefined;
export type ScopeStateType = any;
export { ItemType };

View File

@@ -0,0 +1,114 @@
/*
* 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 { merge } from 'lodash-es';
import {
REPORT_EVENTS as ReportEventNames,
createReportEvent,
} from '@coze-arch/report-events';
import {
type VoicesInfo,
type BotInfoForUpdate,
} from '@coze-arch/idl/playground_api';
import { BotMode } from '@coze-arch/bot-api/developer_api';
import { type RemoveOptional, type UnionUndefined } from '@/types/utils';
import { useQueryCollectStore } from '@/store/query-collect';
import { usePersonaStore } from '@/store/persona';
import { useMultiAgentStore } from '@/store/multi-agent';
import { useModelStore } from '@/store/model';
import { useBotSkillStore } from '@/store/bot-skill';
import { useBotInfoStore } from '@/store/bot-info';
export const getBotDetailDtoInfo = () => {
const { mode } = useBotInfoStore.getState();
const botSkill = useBotSkillStore.getState();
const multiAgent = useMultiAgentStore.getState();
const model = useModelStore.getState();
const persona = usePersonaStore.getState();
const queryCollect = useQueryCollectStore.getState();
const isMulti = mode === BotMode.MultiMode;
const {
knowledge,
variables,
workflows,
taskInfo,
suggestionConfig,
onboardingContent,
pluginApis,
backgroundImageInfoList,
shortcut,
tts,
timeCapsule,
filebox,
devHooks,
voicesInfo,
} = botSkill;
const { agents } = multiAgent;
const reportEvent = createReportEvent({
eventName: ReportEventNames.botDebugSaveAll,
});
try {
const botSkillInfo: Omit<BotInfoForUpdate, 'voices_info'> & {
voices_info: UnionUndefined<RemoveOptional<VoicesInfo>>;
} = {
prompt_info: persona.transformVo2Dto(persona.systemMessage),
model_info: model.transformVo2Dto(model.config),
plugin_info_list: isMulti
? undefined
: botSkill.transformVo2Dto.plugin(pluginApis),
workflow_info_list: isMulti
? undefined
: botSkill.transformVo2Dto.workflow(workflows),
knowledge: isMulti
? undefined
: botSkill.transformVo2Dto.knowledge(knowledge),
variable_list: botSkill.transformVo2Dto.variables(variables),
task_info: botSkill.transformVo2Dto.task(taskInfo),
suggest_reply_info:
botSkill.transformVo2Dto.suggestionConfig(suggestionConfig),
onboarding_info: botSkill.transformVo2Dto.onboarding(onboardingContent),
background_image_info_list: backgroundImageInfoList,
shortcut_sort: botSkill.transformVo2Dto.shortcut(shortcut),
voices_info: merge(
{},
botSkill.transformVo2Dto.tts(tts),
botSkill.transformVo2Dto.voicesInfo(voicesInfo),
),
bot_tag_info: botSkill.transformVo2Dto.timeCapsule(timeCapsule),
filebox_info: botSkill.transformVo2Dto.filebox(filebox),
hook_info: isMulti ? undefined : devHooks,
user_query_collect_conf: queryCollect.transformVo2Dto(queryCollect),
agents: isMulti
? agents.map(item => multiAgent.transformVo2Dto.agent(item))
: undefined,
};
reportEvent.success();
return { botSkillInfo };
} catch (e) {
reportEvent.error({
reason: 'bot debug save all fail',
error: e instanceof Error ? e : void 0,
});
return {};
}
};

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 dayjs from 'dayjs';
import { REPORT_EVENTS as ReportEventNames } from '@coze-arch/report-events';
import { reporter } from '@coze-arch/logger';
import { type BotInfoForUpdate } from '@coze-arch/idl/playground_api';
import { BotPageFromEnum } from '@coze-arch/bot-typings/common';
import { type UpdateDraftBotResponse } from '@coze-arch/bot-api/developer_api';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { useBotInfoStore } from '@/store/bot-info';
import { type BizKey } from '../types';
import { storage } from '../../utils/storage';
import { usePageRuntimeStore } from '../../store/page-runtime';
import { useCollaborationStore } from '../../store/collaboration';
export async function saveFetcher(
saveRequest: () => Promise<UpdateDraftBotResponse>,
scopeKey: BizKey,
) {
const { editable, isPreview, pageFrom, init, setPageRuntimeByImmer } =
usePageRuntimeStore.getState();
const { setCollaborationByImmer } = useCollaborationStore.getState();
const isReadonly = () =>
!editable || isPreview || pageFrom === BotPageFromEnum.Explore;
if (isReadonly() || !init) {
return;
}
try {
setPageRuntimeByImmer(state => {
state.savingInfo.saving = true;
state.savingInfo.scopeKey = scopeKey ? String(scopeKey) : '';
});
const res = await saveRequest();
setPageRuntimeByImmer(state => {
state.savingInfo = {
saving: false,
time: dayjs().format('HH:mm:ss'),
};
});
if (res) {
setPageRuntimeByImmer(state => {
state.hasUnpublishChange = res.data.has_change ?? false;
});
setCollaborationByImmer(state => {
state.sameWithOnline = res.data.same_with_online ?? false;
if (state.branch && res.data.branch) {
state.branch = res.data.branch;
}
});
}
reporter.successEvent({
eventName: ReportEventNames.AutosaveSuccess,
meta: { itemType: scopeKey },
});
} catch (e) {
reporter.errorEvent({
eventName: ReportEventNames.AutosaveError,
error: e as Error,
meta: { itemType: scopeKey },
});
}
}
/**
* 更新bot草稿信息的结构
* @returns 根据标记返回使用不同请求体的更新bot
*/
export function updateBotRequest(structPayload: BotInfoForUpdate) {
const { botId } = useBotInfoStore.getState();
return PlaygroundApi.UpdateDraftBotInfoAgw({
bot_info: {
bot_id: botId,
...structPayload,
},
base_commit_version: storage.baseVersion,
});
}

View File

@@ -0,0 +1,101 @@
/*
* 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 GetBotVersionInfoData,
GetBotVersionScene,
type GetDraftBotInfoAgwData,
} from '@coze-arch/idl/playground_api';
import {
MonetizationEntityType,
type BotMonetizationConfigData,
} from '@coze-arch/bot-api/benefit';
import { benefitApi, PlaygroundApi } from '@coze-arch/bot-api';
export const getBotDataService = async (params: {
scene: 'bot' | 'market';
botId: string;
customVersion?: string;
botInfoVersion: string;
}): Promise<{
botData: GetDraftBotInfoAgwData;
monetizeConfig: BotMonetizationConfigData | undefined;
}> => {
const { scene, botId, customVersion, botInfoVersion } = params;
if (scene === 'bot') {
const [botInfoResp, monetizeConfigResp] = await getBotSceneData({
botId,
version: customVersion ?? '',
});
return {
botData: getCommonBotData(botInfoResp?.data ?? {}),
monetizeConfig: monetizeConfigResp?.data,
};
}
const botInfoResp = await getMarketSceneData({
botId,
version: botInfoVersion,
});
return {
botData: getCommonBotData(botInfoResp?.data ?? {}),
monetizeConfig: undefined,
};
};
const getBotSceneData = async (params: { botId: string; version: string }) => {
const { botId, version } = params;
return await Promise.all([
PlaygroundApi.GetDraftBotInfoAgw({
bot_id: botId,
version,
}),
IS_OVERSEA
? benefitApi.PublicGetBotMonetizationConfig({
entity_id: botId,
entity_type: MonetizationEntityType.Bot,
})
: Promise.resolve(undefined),
]);
};
const getMarketSceneData = async (params: {
botId: string;
version: string;
}) => {
const { botId, version } = params;
return await PlaygroundApi.GetBotVersionInfo({
bot_id: botId,
version: version ?? '',
scene: GetBotVersionScene.BotStore,
});
};
export const getCommonBotData = (
botData: GetDraftBotInfoAgwData | GetBotVersionInfoData,
): GetDraftBotInfoAgwData => {
let commonBotData: GetDraftBotInfoAgwData = {
bot_info: {},
};
if ('bot_info' in botData) {
commonBotData = botData;
}
if ('bot_version_info' in botData) {
commonBotData = {
bot_info: botData.bot_version_info?.common_bot_info ?? {},
};
}
return commonBotData;
};

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',
},
),
);

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type PartialRequired } from '@coze-arch/bot-typings/common';
import type { DraftBotApi } from '@coze-arch/bot-api/playground_api';
import type {
AgentInfo,
ModelInfo,
JumpConfig,
MultiAgentSessionType,
} from '@coze-arch/bot-api/developer_api';
import type {
LineType,
WorkflowEdgeJSON,
} from '@flowgram-adapter/free-layout-editor';
import type { BotDetailSkill, BotSuggestionConfig } from './skill';
import type { RequiredBotPrompt } from './persona';
/** multi agent 相关数据 */
export interface BotMultiAgent {
agents: Agent[];
edges: WorkflowEdgeJSON[];
connector_type: LineType;
/** 用于保存 bot 类型节点的 bot 信息 */
botAgentInfos: DraftBotVo[];
/**
* 会话接管方式配置
* 默认为 flow 模式
*/
chatModeConfig: ChatModeConfig;
}
/** 业务用到的 */
export interface AgentBizInfo {
focused?: boolean;
}
export type Agent = PartialRequired<Omit<AgentInfo, 'work_info'>, 'id'> & {
prompt: string;
model: ModelInfo;
skills: Pick<
BotDetailSkill,
'pluginApis' | 'workflows' | 'knowledge' | 'devHooks'
>;
system_info_all: Array<RequiredBotPrompt>;
bizInfo: AgentBizInfo;
jump_config: JumpConfig;
suggestion: BotSuggestionConfig;
};
/** api 返回的 bot 信息中,部分字段是 json本类型是 parse 后的类型 */
export type DraftBotVo = Omit<DraftBotApi, 'work_info'> & {
work_info: {
suggest_reply: BotSuggestionConfig;
};
};
export type ChatModeConfig =
| {
/** 会话接管方式 */
type: MultiAgentSessionType.Flow;
}
| {
/** 会话接管方式 */
type: MultiAgentSessionType.Host;
/** 当前的 host 节点 id */
currentHostId: string;
};
export interface MultiSheetViewOpenState {
left: boolean;
right: boolean;
}

View File

@@ -0,0 +1,94 @@
/*
* 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 TaskNotice,
type PicTask,
type GeneratePicPrompt,
} from '@coze-arch/idl/playground_api';
export enum GenerateType {
Static = 'static',
Gif = 'gif',
}
export enum DotStatus {
Generating = 1,
Success,
Fail,
Cancel,
None,
}
export interface GenerateGifInfo {
loading: boolean;
dotStatus: DotStatus;
text: string;
image: PicTask;
}
export interface GenerateAvatarModal {
visible: boolean;
activeKey: GenerateType;
selectedImage: PicTask;
generatingTaskId?: string;
gif: GenerateGifInfo;
image: {
loading: boolean;
dotStatus: DotStatus;
text: string;
textCustomizable: boolean;
};
}
export interface GenerateBackGroundModal {
activeKey: GenerateType;
selectedImage: PicTask;
generatingTaskId?: string;
gif: GenerateGifInfo;
image: {
loading: boolean;
dotStatus: DotStatus;
promptInfo: GeneratePicPrompt;
};
}
// 异步生成图片的状态
export interface GenerateImageState {
// 候选图列表信息
imageList: PicTask[];
// 生成图片消息信息
noticeList: TaskNotice[];
// 头像弹窗内生成图片的状态
generateAvatarModal: GenerateAvatarModal;
// 背景图弹窗内生成图片的状态
generateBackGroundModal: GenerateBackGroundModal;
}
export interface GenerateImageAction {
updateImageList: (list: PicTask[]) => void;
pushImageList: (image: PicTask) => void;
// updateImageList: (update: (state: PicTask[]) => void) => void;
updateNoticeList: (list: TaskNotice[]) => void;
setGenerateAvatarModal: (state: GenerateAvatarModal) => void;
resetGenerateAvatarModal: () => void;
setGenerateAvatarModalByImmer: (
update: (state: GenerateAvatarModal) => void,
) => void;
setGenerateBackgroundModalByImmer: (
update: (state: GenerateBackGroundModal) => void,
) => void;
clearGenerateImageStore: () => void;
}

View File

@@ -0,0 +1,24 @@
/*
* 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 { Model, ModelInfo } from '@coze-arch/bot-api/developer_api';
/** 模型设置 */
export interface BotDetailModel {
config: ModelInfo;
/** 全部可选模型 */
modelList: Model[];
}

View File

@@ -0,0 +1,24 @@
/*
* 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 { BotPrompt, PromptType } from '@coze-arch/bot-api/developer_api';
export interface RequiredBotPrompt extends BotPrompt {
prompt_type: PromptType;
data: string;
isOptimize: boolean;
record_id?: string;
}

View File

@@ -0,0 +1,260 @@
/*
* 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 ShortCutStruct } from '@coze-agent-ide/tool-config/src/shortcut-config/type';
import {
type PluginStatus,
type PluginType,
} from '@coze-arch/idl/plugin_develop';
import { type WorkflowMode } from '@coze-arch/bot-api/workflow_api';
import {
type HookInfo,
type LayoutInfo,
type BackgroundImageInfo,
type SuggestedQuestionsShowMode,
type DisablePromptCalling,
type RecallStrategy,
type DefaultUserInputType,
} from '@coze-arch/bot-api/playground_api';
import {
type BotTableRWMode,
type FieldItem,
} from '@coze-arch/bot-api/memory';
import { type Dataset } from '@coze-arch/bot-api/knowledge';
import type {
FileboxInfo,
PluginApi,
PluginParameter,
TaskInfoData,
TaskInfo,
SuggestReplyMode,
} from '@coze-arch/bot-api/developer_api';
interface DefaultPluginApi extends PluginApi {
isAuto?: boolean;
autoAddCss?: boolean;
// #region api所在的 plugin 维度字段
plugin_type?: PluginType;
is_official?: boolean;
plugin_icon?: string;
status?: PluginStatus;
// #endregion
}
export type EnabledPluginApi = Omit<DefaultPluginApi, 'debug_example'>;
export interface BotDetailSkill {
// 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;
// 时间胶囊
timeCapsule: TimeCapsuleConfig;
filebox: FileboxConfig;
// 聊天背景图
backgroundImageInfoList: BackgroundImageInfo[];
// 快捷指令
shortcut: ShortCutStruct;
// hooks
devHooks?: HookInfo;
layoutInfo: LayoutInfo;
}
export interface TaskManageInfo {
user_task_allowed: boolean;
/** 请求task loading状态业务使用 */
loading: boolean;
/** task接口数据业务使用 */
data: TaskInfoData[];
/** 新版task接口数据业务使用 */
task_list: TaskInfo[];
}
export enum VariableKeyErrType {
KEY_CHECK_PASS = 0, // 检查通过
KEY_NAME_USED = 1, // 名称被占用
KEY_IS_NULL = 2, // 为空值
}
export interface TableMemoryItem extends FieldItem {
errorMapper?: Record<string, string[]>;
disableMustRequired?: boolean;
nanoid?: string;
/**
* 是否是内置字段
* @description 内置字段: 仅作展示用,用户不可修改,不可创建同名字段
*/
isSystemField?: boolean;
}
export interface DatabaseInfo {
tableId: string;
name: string;
desc: string;
icon_uri?: string;
extra_info?: Record<string, string>;
readAndWriteMode: BotTableRWMode;
tableMemoryList: TableMemoryItem[];
}
export type DatabaseList = DatabaseInfo[];
export interface VariableItem {
id?: string;
key?: string;
description?: string;
enable?: boolean;
channel?: string;
default_value?: string;
errType?: VariableKeyErrType;
is_system?: boolean;
prompt_disabled?: boolean;
is_disabled?: boolean;
}
export interface TagListType {
tagName: string;
key: string;
id: string;
name: string;
style_id: string;
language_code: string;
language_name: string;
}
export interface ChatVoiceType {
key?: string;
id: string;
name?: string;
style_id: string;
language_code: string;
language_name?: string;
}
export interface DebugStateType {
bot_id: string;
voice_id?: string;
enable?: boolean;
style_id?: string;
}
export interface TTSInfo {
muted: boolean;
close_voice_call: boolean;
i18n_lang_voice: Record<string, number>;
i18n_lang_voice_str: Record<string, string>;
autoplay: boolean;
autoplay_voice: Record<string, number>;
tag_list?: TagListType[];
chatVoiceList?: ChatVoiceType[];
debugVoice: DebugStateType[];
}
export enum TimeCapsuleOptionsEnum {
ON = 1,
OFF = 0,
}
export interface TimeCapsuleConfig {
time_capsule_mode: TimeCapsuleOptionsEnum;
disable_prompt_calling: DisablePromptCalling;
time_capsule_time_to_live: string;
}
export interface WorkFlowItemType {
workflow_id: string;
plugin_id: string;
name: string;
desc: string;
parameters: Array<PluginParameter>;
plugin_icon: string;
flow_mode?: WorkflowMode;
}
export interface KnowledgeConfig {
/** 已选的 knowledge */
dataSetList: Array<Dataset>;
dataSetInfo: {
min_score: number;
search_strategy?: number;
top_k: number;
auto: boolean;
show_source?: boolean;
no_recall_reply_mode?: number;
no_recall_reply_customize_prompt?: string;
show_source_mode?: number;
recall_strategy?: RecallStrategy;
};
}
export interface SuggestQuestionMessage {
id: string;
content: string;
highlight?: boolean;
}
export interface ExtendOnboardingContent {
prologue: string;
suggested_questions: SuggestQuestionMessage[];
suggested_questions_show_mode: SuggestedQuestionsShowMode;
}
export interface BotSuggestionConfig {
/** 0 开启; 1 自定义; 2 关闭; 3 跟随 bot仅 agentflow bot 节点) */
suggest_reply_mode: SuggestReplyMode;
customized_suggest_prompt: string;
}
export interface FileboxConfig {
mode: FileboxInfo['mode'];
}
export interface VoicesInfo {
defaultUserInputType: DefaultUserInputType | undefined;
}

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.
*/
export type RemoveOptional<T> = {
[K in keyof T]-?: T[K];
};
export type UnionUndefined<T> = {
[K in keyof T]: T[K] | undefined;
};

View File

@@ -0,0 +1,256 @@
/*
* 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 { withSlardarIdButton } from '@coze-studio/bot-utils';
import { logger } from '@coze-arch/logger';
import { I18n } from '@coze-arch/i18n';
import { arrayBufferToObject } from '@coze-arch/bot-utils';
import {
type GenPicMessage,
PicType,
} from '@coze-arch/bot-api/playground_api';
import { PlaygroundApi } from '@coze-arch/bot-api';
import webSocketManager, {
type Connection,
type FrontierEventMap,
} from '@coze-common/websocket-manager-adapter';
import { Toast } from '@coze-arch/coze-design';
import { getBotDetailIsReadonly } from '../get-read-only';
import { DotStatus } from '../../types/generate-image';
import { useGenerateImageStore } from '../../store/generate-image-store';
import { useBotInfoStore } from '../../store/bot-info';
class AvatarBackgroundWebSocket {
private connection: Connection | undefined;
private eventListenerList:
| Array<{
key: keyof FrontierEventMap;
listener: (event) => void;
}>
| undefined;
private biz: string;
private service: number | undefined;
private taskSet = new Set();
constructor(biz: string, service: number) {
this.biz = biz;
this.service = service;
}
createConnection(retry = true) {
if (this.connection) {
return;
}
try {
this.connection = webSocketManager.createConnection({
biz: this.biz,
service: this.service,
});
this.addWSEventListener();
} catch (error) {
// 重试一次
if (retry) {
this.createConnection(false);
}
}
}
destroy() {
if (this.connection) {
this.eventListenerList?.forEach(({ key, listener }) => {
this.connection?.removeEventListener(key, listener);
});
this.connection?.close();
this.connection = undefined;
}
}
private addWSEventListener() {
this.eventListenerList = [
{ key: 'message', listener: this.onSocketMessage },
{ key: 'error', listener: this.onSocketError },
];
this.eventListenerList?.forEach(({ key, listener }) => {
this.connection?.addEventListener(key, listener);
});
}
private onSocketMessage = event => {
const payload = arrayBufferToObject(
event?.message?.payload,
) as GenPicMessage;
const task = payload?.pic_task;
const taskId = task?.id || '';
if (this.taskSet.has(taskId)) {
logger.info({
message: 'duplicate task',
meta: { taskId },
});
return;
}
this.taskSet.add(taskId);
const botId = useBotInfoStore.getState().botId || '0';
if (botId !== '0' && getBotDetailIsReadonly()) {
return;
}
const taskBotId = task?.bot_id || '0';
if (task && taskBotId === botId) {
const {
generateAvatarModal,
generateBackGroundModal,
setGenerateAvatarModalByImmer,
setGenerateBackgroundModalByImmer,
pushImageList,
} = useGenerateImageStore.getState();
const {
gif: { dotStatus: avatarGifDotStatus },
image: { dotStatus: avatarStaticImageDotStatus },
} = generateAvatarModal;
const {
gif: { dotStatus: backgroundGifDotStatus },
image: { dotStatus: backgroundStaticImageDotStatus },
} = generateBackGroundModal;
const { status } = task;
// 收到消息后更新头像或背景
const updateState = (
key: string,
setImmer:
| typeof setGenerateAvatarModalByImmer
| typeof setGenerateBackgroundModalByImmer,
currentDotStatus: DotStatus,
) => {
let dotStatus = DotStatus.None;
if (currentDotStatus === DotStatus.Generating) {
dotStatus =
(status as number) === DotStatus.Success
? DotStatus.Success
: DotStatus.Fail;
} else {
// 标为已读
if (taskBotId !== '0') {
PlaygroundApi.MarkReadNotice({
bot_id: taskBotId,
pic_type: task.type,
});
}
if ((status as number) === DotStatus.Fail) {
Toast.error({
content: withSlardarIdButton(
payload?.err_msg || I18n.t('profilepicture_toast_failed'),
),
});
} else if ((status as number) === DotStatus.Success) {
Toast.success(I18n.t('profilepicture_toast_generated'));
}
}
setImmer(state => {
state[key] = {
...state[key],
loading: false,
dotStatus,
generateTaskId: '',
};
if ((status as number) === DotStatus.Success) {
state.selectedImage = task;
}
});
if ((status as number) === DotStatus.Success) {
pushImageList(task);
}
};
switch (task.type) {
case PicType.IconGif: {
updateState('gif', setGenerateAvatarModalByImmer, avatarGifDotStatus);
break;
}
case PicType.IconStatic: {
updateState(
'image',
setGenerateAvatarModalByImmer,
avatarStaticImageDotStatus,
);
break;
}
case PicType.BackgroundGif: {
updateState(
'gif',
setGenerateBackgroundModalByImmer,
backgroundGifDotStatus,
);
break;
}
case PicType.BackgroundStatic: {
updateState(
'image',
setGenerateBackgroundModalByImmer,
backgroundStaticImageDotStatus,
);
break;
}
default:
}
}
};
private onSocketError = event => {
// TODO
};
}
const getPluginServiceId = () => {
// region/service_id映射
const regionServiceIdMap = {
boe: 16778137,
cn: 33554636,
sg: 67108932,
va: 67108932,
};
return regionServiceIdMap[IS_BOE ? 'boe' : REGION];
};
const serviceID = getPluginServiceId();
export const avatarBackgroundWebSocket = new AvatarBackgroundWebSocket(
'EditorPic',
serviceID,
);
export function initAvatarBackgroundWebSocket() {
// 创建连接
setTimeout(() => {
const {
generateAvatarModal: {
gif: { dotStatus: avatarGifDotStatus },
image: { dotStatus: avatarStaticImageDotStatus },
},
generateBackGroundModal: {
gif: { dotStatus: backgroundGifDotStatus },
image: { dotStatus: backgroundStaticImageDotStatus },
},
} = useGenerateImageStore.getState();
if (
[
avatarGifDotStatus,
avatarStaticImageDotStatus,
backgroundGifDotStatus,
backgroundStaticImageDotStatus,
].includes(DotStatus.Generating)
) {
avatarBackgroundWebSocket.createConnection();
}
}, 10);
}

View File

@@ -0,0 +1,20 @@
/*
* 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 { globalVars } from '@coze-arch/web-context';
export const getExecuteDraftBotRequestId = (): string =>
globalVars.LAST_EXECUTE_ID;

View File

@@ -0,0 +1,85 @@
/*
* 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 { withSlardarIdButton } from '@coze-studio/bot-utils';
import { I18n } from '@coze-arch/i18n';
import { Toast } from '@coze-arch/bot-semi';
import { AgentType } from '@coze-arch/bot-api/developer_api';
import type { BotMultiAgent, Agent } from '../types/agent';
export const findFirstAgent = (
multiAgent: BotMultiAgent,
): Agent | undefined => {
const startNode = multiAgent.agents.find(
agent => agent.agent_type === AgentType.Start_Agent,
);
if (!startNode) {
Toast.error({
content: withSlardarIdButton(I18n.t('chatflow_error_miss_start')),
});
return;
}
const firstAgentId = multiAgent.edges.find(
edge => edge.sourceNodeID === startNode.id,
)?.targetNodeID;
if (!firstAgentId) {
Toast.error({
content: withSlardarIdButton(I18n.t('chatflow_error_miss_start_agent')),
});
return;
}
return findTargetAgent(multiAgent.agents, firstAgentId);
};
export const findTargetAgent = (agents: Agent[], agentId?: string) => {
if (!agentId) {
return;
}
return agents.find(item => item.id === agentId);
};
/** 寻找某个agent其中该agent的intent的next_agent_id是当前的agent id */
export const findAgentByNextIntentID = (
agents: Agent[],
nextAgentID?: string,
) => {
if (!nextAgentID) {
return;
}
return agents.find(item =>
(item.intents || []).some(intent => intent.next_agent_id === nextAgentID),
);
};
export const findTargetAgentIndex = (agents: Agent[], agentId?: string) => {
if (!agentId) {
return -1;
}
return agents.findIndex(item => item.id === agentId);
};
/**
* start 节点指向的节点 id
*/
export const findFirstAgentId = ({
agents,
}: Pick<BotMultiAgent, 'agents'>): string | undefined => {
const startNode = agents.find(
agent => agent.agent_type === AgentType.Start_Agent,
);
return startNode?.intents?.at(0)?.next_agent_id;
};

View File

@@ -0,0 +1,165 @@
/*
* 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 {
GenPicStatus,
PicType,
type GetPicTaskData,
} from '@coze-arch/idl/playground_api';
import {
type GenerateBackGroundModal,
type GenerateAvatarModal,
DotStatus,
GenerateType,
} from '../types/generate-image';
import { useBotSkillStore } from '../store/bot-skill';
import getDotStatus from './get-dot-status';
export const getInitBackgroundInfo = (
data: GetPicTaskData,
state: GenerateBackGroundModal,
) => {
const { tasks = [] } = data;
const { backgroundImageInfoList = [] } = useBotSkillStore.getState();
// 当前渲染的背景图
const uri =
backgroundImageInfoList[0]?.mobile_background_image?.origin_image_uri;
const backgroundGifList = tasks.filter(
item => item.type && [PicType.BackgroundGif].includes(item.type),
);
const backgroundStaticList = tasks.filter(
item => item.type && [PicType.BackgroundStatic].includes(item.type),
);
const imageDotStatus = getDotStatus(
data,
PicType.BackgroundStatic,
) as DotStatus;
const gifDotStatus = getDotStatus(data, PicType.BackgroundGif) as DotStatus;
// 动图相关state
state.gif.loading = backgroundGifList.some(
item => item.status === GenPicStatus.Generating,
);
state.gif.text =
backgroundGifList.find(item => item?.img_info?.prompt)?.img_info?.prompt
?.ori_prompt ?? '';
state.gif.dotStatus = gifDotStatus;
const image = backgroundGifList.find(item => item.img_info?.ori_url);
// 第一帧信息
if (image) {
state.gif.image = {
img_info: {
tar_uri: image.img_info?.ori_uri,
tar_url: image.img_info?.ori_url,
},
};
}
// 静图相关state
state.image.loading = backgroundStaticList.some(
item => item.status === GenPicStatus.Generating,
);
state.image.dotStatus = imageDotStatus;
state.image.promptInfo =
backgroundStaticList.find(item => item?.img_info?.prompt?.ori_prompt)
?.img_info?.prompt ?? {};
const lastImageTask =
tasks.find(item => item.type === PicType.BackgroundStatic) ?? {};
const lastGifTask =
tasks.find(item => item.type === PicType.BackgroundGif) ?? {};
// 当前选中的图片: 生成成功的 展示 成功的那个图, 否则找 背景图一致的
if (gifDotStatus === DotStatus.Success) {
state.selectedImage = lastGifTask;
} else if (imageDotStatus === DotStatus.Success) {
state.selectedImage = lastImageTask;
} else {
// 手动上传的 找不到
state.selectedImage =
tasks.find(item => item.img_info?.tar_uri === uri) ?? {};
}
// 当前tab只有在 仅gif在状态不为done时 在gif tab
if (gifDotStatus !== DotStatus.None) {
state.activeKey = GenerateType.Gif;
}
// 当前正在生成的taskId
if (
gifDotStatus === DotStatus.Generating ||
imageDotStatus === DotStatus.Generating
) {
state.generatingTaskId =
gifDotStatus === DotStatus.Generating
? lastGifTask?.id
: lastImageTask?.id;
}
};
export const getInitAvatarInfo = (
data: GetPicTaskData,
state: GenerateAvatarModal,
) => {
const { tasks = [] } = data || {};
const lastImageTask = tasks.find(
item => item.type === PicType.IconStatic,
) || {
id: '',
img_info: {},
};
const lastGifTask = tasks.find(item => item.type === PicType.IconGif) || {
id: '',
img_info: {},
};
const gifDotStatus = getDotStatus(data, PicType.IconGif) as DotStatus;
const imageDotStatus = getDotStatus(data, PicType.IconStatic) as DotStatus;
if (
gifDotStatus === DotStatus.Success ||
imageDotStatus === DotStatus.Success
) {
state.selectedImage =
gifDotStatus === DotStatus.Success ? lastGifTask : lastImageTask;
}
if (
gifDotStatus === DotStatus.Generating ||
imageDotStatus === DotStatus.Generating
) {
state.generatingTaskId =
gifDotStatus === DotStatus.Generating
? lastGifTask?.id
: lastImageTask?.id;
}
state.gif = {
dotStatus: gifDotStatus,
text: lastGifTask?.img_info?.prompt?.ori_prompt ?? '',
loading: gifDotStatus === DotStatus.Generating,
image: {
id: lastGifTask.img_info?.ori_uri ?? '',
img_info: {
tar_uri: lastGifTask.img_info?.ori_uri ?? '',
tar_url: lastGifTask.img_info?.ori_url ?? '',
},
},
};
state.image = {
dotStatus: imageDotStatus,
text: lastImageTask.img_info?.prompt?.ori_prompt ?? '',
loading: imageDotStatus === DotStatus.Generating,
textCustomizable: Boolean(lastImageTask.img_info?.prompt?.ori_prompt),
};
};

View File

@@ -0,0 +1,33 @@
/*
* 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 PicType,
type GetPicTaskData,
} from '@coze-arch/idl/playground_api';
import { DotStatus } from '../types/generate-image';
function getDotStatus(data: GetPicTaskData, picType: PicType) {
const { notices = [], tasks = [] } = data || {};
const task = tasks.find(item => item.type === picType);
return (task?.status as number) === DotStatus.Generating ||
notices.some(item => item.type === picType && item.un_read)
? task?.status ?? DotStatus.None
: DotStatus.None;
}
export default getDotStatus;

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { usePageRuntimeStore } from '../store/page-runtime';
import { useCollaborationStore, EditLockStatus } from '../store/collaboration';
/**
* 非响应式;参考 useBotDetailIsReadonly 方法
*/
export function getBotDetailIsReadonly() {
const pageRuntime = usePageRuntimeStore.getState();
const collaboration = useCollaborationStore.getState();
return getBotDetailIsReadonlyByState({
editable: pageRuntime.editable,
isPreview: pageRuntime.isPreview,
editLockStatus: collaboration.editLockStatus,
});
}
export const getBotDetailIsReadonlyByState = ({
editable,
isPreview,
editLockStatus,
}: {
editable: boolean;
isPreview: boolean;
editLockStatus?: EditLockStatus;
}) => !editable || isPreview || editLockStatus === EditLockStatus.Lose;

View File

@@ -0,0 +1,63 @@
/*
* 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 { Toast } from '@coze-arch/bot-semi';
import { MultiAgentSessionType } from '@coze-arch/bot-api/playground_api';
import { useMultiAgentStore } from '../store/multi-agent';
import { useManuallySwitchAgentStore } from '../store/manually-switch-agent-store';
import { saveDeleteAgents } from '../save-manager/manual-save/multi-agent';
import { findTargetAgentIndex } from './find-agent';
/**
* FG全量后默认用结构化的新接口
*/
export const deleteAgent = async (agentId?: string) => {
if (!agentId) {
return;
}
await saveDeleteAgents(agentId);
useMultiAgentStore.getState().setMultiAgentByImmer(multiAgent => {
const { agents } = multiAgent;
// 找到要删除的位置
const targetAgentIndex = findTargetAgentIndex(agents, agentId);
if (targetAgentIndex < 0) {
Toast.error(I18n.t('chatflow_error_delete_failed'));
return;
}
// 删除当前的agent
agents.splice(targetAgentIndex, 1);
});
};
/**
* 用户手动切换 chatting 节点
*
* host 模式下会一并切换 host 节点
*/
export const manuallySwitchAgent = (agentID: string) => {
const { setMultiAgentByImmer } = useMultiAgentStore.getState();
useManuallySwitchAgentStore
.getState()
.recordAgentIdOnManuallySwitchAgent(agentID);
setMultiAgentByImmer(multiAgent => {
multiAgent.currentAgentID = agentID;
if (multiAgent.chatModeConfig.type === MultiAgentSessionType.Host) {
multiAgent.chatModeConfig.currentHostId = agentID;
}
});
};

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type Branch, type Committer } from '@coze-arch/bot-api/developer_api';
import { useCollaborationStore } from '../store/collaboration';
interface HeaderStatusType {
branch?: Branch;
same_with_online?: boolean;
committer?: Committer;
commit_version?: string;
}
export function updateHeaderStatus(props: HeaderStatusType) {
const { setCollaborationByImmer } = useCollaborationStore.getState();
setCollaborationByImmer(store => {
store.sameWithOnline = props.same_with_online ?? false;
if (props.committer) {
store.commit_time = props.committer.commit_time ?? '';
store.committer_name = props.committer.name ?? '';
}
if (props.commit_version) {
store.commit_version = props.commit_version;
store.baseVersion = props.commit_version;
}
if (props.branch) {
store.branch = props.branch;
}
});
}

View File

@@ -0,0 +1,29 @@
/*
* 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 { omit } from 'lodash-es';
import type { PluginApi } from '@coze-arch/bot-api/playground_api';
import { type EnabledPluginApi } from '../types/skill';
// 过滤 debug_example 字段 以免超出模型解析长度
export const getPluginApisFilterExample = (
pluginApis: PluginApi[],
): EnabledPluginApi[] => pluginApis.map(item => omit(item, 'debug_example'));
export const getSinglePluginApiFilterExample = (
tool: PluginApi,
): EnabledPluginApi => omit(tool, 'debug_example');

View File

@@ -0,0 +1,35 @@
/*
* 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 { PromptType } from '@coze-arch/bot-api/developer_api';
export function replacedBotPrompt(data) {
return [
{
prompt_type: PromptType.SYSTEM,
data: data.data,
record_id: data.record_id,
},
{
prompt_type: PromptType.USERPREFIX,
data: '',
},
{
prompt_type: PromptType.USERSUFFIX,
data: '',
},
];
}

View File

@@ -0,0 +1,42 @@
/*
* 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 BotPrompt, PromptType } from '@coze-arch/bot-api/developer_api';
import { usePersonaStore } from '../store/persona';
export interface SaveBotPrompt extends BotPrompt {
id: string;
}
export const getReplacedBotPrompt = () => {
const { systemMessage } = usePersonaStore.getState();
return [
{
prompt_type: PromptType.SYSTEM,
data: systemMessage.data,
},
{
prompt_type: PromptType.USERPREFIX,
data: '',
},
{
prompt_type: PromptType.USERSUFFIX,
data: '',
},
];
};

View File

@@ -0,0 +1,50 @@
/*
* 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 create } from 'zustand';
export interface SetterAction<T> {
/**
* 增量更新
*
* @example
* // store.x: { a: 1, b: 2 }
* setX({a: 2});
* // store.x: { a: 2, b: 2 }
*/
(state: Partial<T>): void;
/**
* 全量更新
*
* @example
* // store.x: { a: 1, b: 2 }
* setX({a: 2}, { replace: true });
* // store.x: { a: 2 }
*/
(state: T, config: { replace: true }): void;
}
export function setterActionFactory<T>(
set: Parameters<Parameters<typeof create<T, []>>[0]>[0],
): SetterAction<T> {
return (state: Partial<T>, config?: { replace: true }) => {
if (config?.replace) {
set(state);
} else {
set(prevState => ({ ...prevState, ...state }));
}
};
}

View File

@@ -0,0 +1,63 @@
/*
* 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 { useCollaborationStore } from '../store/collaboration';
export function createStorage<T extends object>(
s: Storage,
target: T,
prefix = 'common_storage',
) {
return new Proxy(target, {
set: (_, prop: string, value) => {
if (typeof value === 'string') {
s.setItem(`${prefix}.${prop}`, value);
return true;
}
return false;
},
get: (_, prop: string) => s.getItem(`${prefix}.${prop}`) ?? undefined,
deleteProperty: (_, prop): boolean => {
if (typeof prop === 'string') {
s.removeItem(`${prefix}.${prop}`);
}
return true;
},
});
}
export const storageLocal = createStorage<Record<string, string | undefined>>(
localStorage,
{},
);
// NOTICE: 定制逻辑: baseVersion转从 bot_detail_store中获取
export const storage = new Proxy(storageLocal, {
get: (target, prop: string, receiver) => {
if (prop === 'baseVersion') {
return useCollaborationStore.getState().getBaseVersion();
}
return Reflect.get(target, prop, receiver);
},
set(target, prop, ...rest) {
if (prop === 'baseVersion') {
console.error(
'you should use botDetailStore instead of storage to keep base_commit_version',
);
return false;
}
return Reflect.set(target, prop, ...rest);
},
});

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 { I18n } from '@coze-arch/i18n';
import { UIToast } from '@coze-arch/bot-semi';
export const hasBraces = (str: string) => {
const pattern = /{{/g;
return pattern.test(str);
};
// 判断是所有环境还是 只是release 环境限制{{}} 并弹出toast提示
export const verifyBracesAndToast = (str: string, isAll = false) => {
if (isAll && hasBraces(str)) {
UIToast.warning({
showClose: false,
content: I18n.t('bot_prompt_bracket_error'),
});
return false;
}
return true;
};

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type VariableItem, VariableKeyErrType } from '../types/skill';
export function uniqMemoryList(
list: VariableItem[],
sysVariables: VariableItem[] = [],
) {
return list.map(i => {
const res = { ...i };
if (
list.filter(j => j.key === i.key).length === 1 &&
sysVariables.filter(v => v.key === i.key)?.length === 0
) {
res.errType = VariableKeyErrType.KEY_CHECK_PASS;
} else {
res.errType = VariableKeyErrType.KEY_NAME_USED;
}
if (!i.key) {
res.errType = VariableKeyErrType.KEY_IS_NULL;
}
return res;
});
}