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