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,31 @@
import { mergeConfig } from 'vite';
import svgr from 'vite-plugin-svgr';
/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.tsx'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-onboarding',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
viteFinal: config =>
mergeConfig(config, {
plugins: [
svgr({
svgrOptions: {
native: false,
},
}),
],
}),
};
export default config;

View File

@@ -0,0 +1,14 @@
/** @type { import('@storybook/react').Preview } */
const preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;

View File

@@ -0,0 +1,5 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
});

View File

@@ -0,0 +1,16 @@
# @coze-agent-ide/bot-editor-context-store
限定在 bot 编辑页面 context 的 store。通过 react context 下发。
## Features
- [x] eslint & ts
- [x] esm bundle
- [x] umd bundle
- [x] storybook
## Commands
- init: `rush update`
- dev: `npm run dev`
- build: `npm run build`

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["./dist"]
}
]
}

View File

@@ -0,0 +1,7 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {},
});

View File

@@ -0,0 +1,51 @@
{
"name": "@coze-agent-ide/bot-editor-context-store",
"version": "0.0.1",
"description": "Bot Editor store with context",
"license": "Apache-2.0",
"author": "gaoyuanhan.duty@bytedance.com",
"maintainers": [],
"main": "src/index.ts",
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"classnames": "^2.3.2"
},
"devDependencies": {
"@coze-arch/bot-api": "workspace:*",
"@coze-arch/bot-typings": "workspace:*",
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/stylelint-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@coze-studio/bot-detail-store": "workspace:*",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/lodash-es": "^4.17.10",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@vitest/coverage-v8": "~3.0.5",
"ahooks": "^3.7.8",
"immer": "^10.0.3",
"lodash-es": "^4.17.21",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"stylelint": "^15.11.0",
"vite-plugin-svgr": "~3.3.0",
"vitest": "~3.0.5",
"zustand": "^4.4.7"
},
"peerDependencies": {
"ahooks": "^3.7.8",
"immer": "^10.0.3",
"react": ">=18.2.0",
"react-dom": ">=18.2.0",
"zustand": "^4.4.7"
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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 PropsWithChildren, createContext, useEffect } from 'react';
import { useCreation } from 'ahooks';
import { createOnboardingDirtyLogicCompatibilityStore } from '../../store/onboarding-dirty-logic-compatibility';
import { createNLPromptModalStore } from '../../store/nl-prompt-modal';
import { createModelStore } from '../../store/model';
import { createFreeGrabModalHierarchyStore } from '../../store/free-grab-modal-hierarchy';
import { createDraftBotDatasetsStore } from '../../store/dataset';
import { createDraftBotPluginsStore } from '../../store/bot-plugins';
import { type BotEditorContextProps } from './type';
export const BotEditorContext = createContext<BotEditorContextProps>({
storeSet: null,
});
export const BotEditorContextProvider: React.FC<PropsWithChildren> = ({
children,
}) => {
const useOnboardingDirtyLogicCompatibilityStore = useCreation(
() => createOnboardingDirtyLogicCompatibilityStore(),
[],
);
const { useModelStore, unSubscribe } = useCreation(
() => createModelStore(),
[],
);
useEffect(() => unSubscribe, []);
const useDraftBotPluginsStore = useCreation(
() => createDraftBotPluginsStore(),
[],
);
const useDraftBotDataSetStore = useCreation(
() => createDraftBotDatasetsStore(),
[],
);
const useNLPromptModalStore = useCreation(
() => createNLPromptModalStore(),
[],
);
const useFreeGrabModalHierarchyStore = useCreation(
() => createFreeGrabModalHierarchyStore(),
[],
);
return (
<BotEditorContext.Provider
value={{
storeSet: {
useOnboardingDirtyLogicCompatibilityStore,
useModelStore,
useDraftBotPluginsStore,
useDraftBotDataSetStore,
useNLPromptModalStore,
useFreeGrabModalHierarchyStore,
},
}}
>
{children}
</BotEditorContext.Provider>
);
};

View File

@@ -0,0 +1,30 @@
/*
* 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 { useContext } from 'react';
import { recordExhaustiveCheck } from '../../utils/exhaustive-check';
import { BotEditorContext } from './context';
export const useBotEditor = () => {
const context = useContext(BotEditorContext);
const { storeSet, ...rest } = context;
recordExhaustiveCheck(rest);
if (!storeSet) {
throw new Error('invalid BotEditorContext');
}
return { storeSet };
};

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 { type OnboardingDirtyLogicCompatibilityStore } from '../../store/onboarding-dirty-logic-compatibility';
import { type NLPromptModalStore } from '../../store/nl-prompt-modal';
import { type ModelStore } from '../../store/model';
import { type FreeGrabModalHierarchyStore } from '../../store/free-grab-modal-hierarchy';
import { type DraftBotDatasetsStore } from '../../store/dataset';
import { type DraftBotPluginsStore } from '../../store/bot-plugins';
export interface StoreSet {
useOnboardingDirtyLogicCompatibilityStore: OnboardingDirtyLogicCompatibilityStore;
useModelStore: ModelStore;
useDraftBotPluginsStore: DraftBotPluginsStore;
useDraftBotDataSetStore: DraftBotDatasetsStore;
useNLPromptModalStore: NLPromptModalStore;
useFreeGrabModalHierarchyStore: FreeGrabModalHierarchyStore;
}
export interface BotEditorContextProps {
storeSet: null | StoreSet;
}

View File

@@ -0,0 +1,75 @@
/*
* 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 { useMultiAgentStore } from '@coze-studio/bot-detail-store/multi-agent';
import { useModelStore as useBotDetailModelStore } from '@coze-studio/bot-detail-store/model';
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
import { BotMode } from '@coze-arch/bot-api/developer_api';
import {
defaultModelCapConfig,
getMultiAgentModelCapabilityConfig,
getSingleAgentModelCapabilityConfig,
type TGetModelCapabilityConfig,
} from '../../utils/model-capability';
import { useBotEditor } from '../../context/bot-editor-context';
const getModelCapabilityConfigMap: Record<BotMode, TGetModelCapabilityConfig> =
{
[BotMode.SingleMode]: getSingleAgentModelCapabilityConfig,
[BotMode.WorkflowMode]: () => defaultModelCapConfig,
[BotMode.MultiMode]: getMultiAgentModelCapabilityConfig,
};
export const useModelCapabilityConfig = () => {
const {
storeSet: { useModelStore },
} = useBotEditor();
const getModelById = useModelStore(store => store.getModelById);
const mode = useBotInfoStore(store => store.mode);
const modelIds = useGetModelIdsByMode(mode);
return getModelCapabilityConfigMap[mode]({
modelIds,
getModelById,
});
};
const useGetModelIdsByMode = (mode: BotMode) => {
const { multiModelIds } = useMultiAgentStore(
useShallow(store => ({
multiModelIds: Array.from(
store.agents
.reduce<Set<string>>((res, agent) => {
if (agent.model.model !== undefined) {
res.add(agent.model.model);
}
return res;
}, new Set())
.values(),
),
})),
);
const singleModeId = useBotDetailModelStore(
store => store.config.model ?? '',
);
const getModeIdsMap: Record<BotMode, string[]> = {
[BotMode.SingleMode]: [singleModeId],
[BotMode.MultiMode]: multiModelIds,
[BotMode.WorkflowMode]: [],
};
return getModeIdsMap[mode];
};

View File

@@ -0,0 +1,45 @@
/*
* 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 { useBotEditor } from './context/bot-editor-context/index';
export { BotEditorContextProvider } from './context/bot-editor-context/context';
export {
convertModelValueType,
type ConvertedModelValueTypeMap,
} from './utils/model/convert-model-value-type';
export { getModelById } from './utils/model/get-model-by-id';
export {
type BotEditorOnboardingSuggestion,
type ModelPresetValues,
type NLPromptModalPosition,
} from './store/type';
export { ModelState, ModelAction } from './store/model';
export type {
NLPromptModalStore,
NLPromptModalAction,
NLPromptModalState,
} from './store/nl-prompt-modal';
export {
FreeGrabModalHierarchyAction,
FreeGrabModalHierarchyState,
FreeGrabModalHierarchyStore,
} from './store/free-grab-modal-hierarchy';
export { useModelCapabilityConfig } from './hooks/model-capability';
export { mergeModelFuncConfigStatus } from './utils/model-capability';
export {
createOnboardingDirtyLogicCompatibilityStore,
type OnboardingDirtyLogicCompatibilityStore,
} from './store/onboarding-dirty-logic-compatibility';

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 { devtools, subscribeWithSelector } from 'zustand/middleware';
import { create } from 'zustand';
import { type PluginInfoForPlayground } from '@coze-arch/bot-api/plugin_develop';
import { PluginDevelopApi } from '@coze-arch/bot-api';
type PluginsIdMap = Record<string, PluginInfoForPlayground>;
export interface DraftBotPluginStoreState {
pluginsMap: PluginsIdMap;
}
export interface DraftBotPluginStoreAction {
batchLoad: (pluginIds: string[], spaceId: string) => Promise<void>;
update: (pluginInfo: PluginInfoForPlayground) => void;
}
const getDefaultState = (): DraftBotPluginStoreState => ({
pluginsMap: {},
});
export const createDraftBotPluginsStore = () =>
create<DraftBotPluginStoreState & DraftBotPluginStoreAction>()(
devtools(
subscribeWithSelector((set, get) => ({
...getDefaultState(),
batchLoad: async (pluginIds, spaceId) => {
const { pluginsMap } = get();
const newPluginIds = pluginIds.filter(id => !pluginsMap[id]);
if (newPluginIds.length) {
const res = await PluginDevelopApi.GetPlaygroundPluginList({
page: 1,
size: pluginIds.length,
plugin_ids: pluginIds,
space_id: spaceId,
is_get_offline: true,
plugin_types: [1],
});
set({
pluginsMap: res.data?.plugin_list?.reduce<PluginsIdMap>(
(map, item) => ({
...map,
[item.id ?? '']: item,
}),
{
...get().pluginsMap,
},
),
});
}
},
update: plugin => {
set({
pluginsMap: {
...get().pluginsMap,
[plugin.id ?? '']: plugin,
},
});
},
})),
),
);
export type DraftBotPluginsStore = ReturnType<
typeof createDraftBotPluginsStore
>;

View File

@@ -0,0 +1,82 @@
/*
* 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 Dataset } from '@coze-arch/bot-api/knowledge';
import { KnowledgeApi } from '@coze-arch/bot-api';
type DatasetsIdMap = Record<string, Dataset>;
export interface DraftBotDataSetStoreState {
datasetsMap: DatasetsIdMap;
}
export interface DraftBotDataSetStoreAction {
batchLoad: (datasetIds: string[], spaceId: string) => Promise<void>;
reset: () => void;
batchUpdate: (datasets: Dataset[]) => void;
}
const getDefaultState = (): DraftBotDataSetStoreState => ({
datasetsMap: {},
});
// 目前 work_info 里的 dataset 只包含了很少量的元信息,
// 为了方便判断引入的 dataset 类型(用于分组、模型能力检查等等),这里统一缓存当下使用的 dataset
export const createDraftBotDatasetsStore = () =>
create<DraftBotDataSetStoreState & DraftBotDataSetStoreAction>()(
devtools(
subscribeWithSelector((set, get) => ({
...getDefaultState(),
reset: () => {
set({
...getDefaultState(),
});
},
batchLoad: async (datasetIds, spaceId) => {
const { datasetsMap } = get();
const newIds = datasetIds.filter(id => !datasetsMap[id]);
if (newIds.length) {
const res = await KnowledgeApi.ListDataset({
filter: {
dataset_ids: newIds,
},
space_id: spaceId,
});
get().batchUpdate(res.dataset_list ?? []);
}
},
batchUpdate: datasets => {
set({
datasetsMap: datasets.reduce<DatasetsIdMap>(
(map, item) => ({
...map,
[item.dataset_id ?? '']: item,
}),
{
...get().datasetsMap,
},
),
});
},
})),
),
);
export type DraftBotDatasetsStore = ReturnType<
typeof createDraftBotDatasetsStore
>;

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 { devtools } from 'zustand/middleware';
import { create } from 'zustand';
import { produce } from 'immer';
export interface FreeGrabModalHierarchyState {
// modal 的 key list
modalHierarchyList: string[];
}
export interface FreeGrabModalHierarchyAction {
registerModal: (key: string) => void;
removeModal: (key: string) => void;
getModalIndex: (key: string) => number;
setModalToTopLayer: (key: string) => void;
}
/**
* 可自由拖拽的弹窗之间的层级关系
*/
export const createFreeGrabModalHierarchyStore = () =>
create<FreeGrabModalHierarchyState & FreeGrabModalHierarchyAction>()(
devtools(
(set, get) => ({
modalHierarchyList: [],
getModalIndex: key =>
get().modalHierarchyList.findIndex(modalKey => modalKey === key),
registerModal: key => {
set(
{
modalHierarchyList: produce(get().modalHierarchyList, draft => {
draft.unshift(key);
}),
},
false,
'registerModal',
);
},
removeModal: key => {
set(
{
modalHierarchyList: produce(get().modalHierarchyList, draft => {
const index = get().getModalIndex(key);
if (index < 0) {
return;
}
draft.splice(index, 1);
}),
},
false,
'removeModal',
);
},
setModalToTopLayer: key => {
set(
{
modalHierarchyList: produce(get().modalHierarchyList, draft => {
const index = get().getModalIndex(key);
if (index < 0) {
return;
}
get().removeModal(key);
get().registerModal(key);
}),
},
false,
'setModalToTopLayer',
);
},
}),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botEditor.ModalHierarchy',
},
),
);
export type FreeGrabModalHierarchyStore = ReturnType<
typeof createFreeGrabModalHierarchyStore
>;

View File

@@ -0,0 +1,57 @@
/*
* 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 } from '@coze-arch/bot-api/developer_api';
import { type ModelPresetValues } from '../type';
import { convertModelValueType } from '../../utils/model/convert-model-value-type';
export const getModelPresetValues = ({
model_params: modelParams,
}: Required<Pick<Model, 'model_params'>>): ModelPresetValues => {
const presetValues: Required<ModelPresetValues> = {
defaultValues: {},
creative: {},
precise: {},
balance: {},
};
modelParams.forEach(param => {
const { default_val: paramPresetValues, name, type } = param;
const defaultValue = paramPresetValues.default_val;
const creativeValue = paramPresetValues.creative;
const balanceValue = paramPresetValues.balance;
const preciseValue = paramPresetValues.precise;
presetValues.defaultValues[name] = convertModelValueType(
defaultValue,
type,
);
if (creativeValue) {
const convertedCreativeValue = convertModelValueType(creativeValue, type);
presetValues.creative[name] = convertedCreativeValue;
}
if (balanceValue) {
const convertedBalanceValue = convertModelValueType(balanceValue, type);
presetValues.balance[name] = convertedBalanceValue;
}
if (preciseValue) {
const convertedPreciseValue = convertModelValueType(preciseValue, type);
presetValues.precise[name] = convertedPreciseValue;
}
});
return presetValues;
};

View File

@@ -0,0 +1,112 @@
/*
* 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 { shallow } from 'zustand/vanilla/shallow';
import { devtools, subscribeWithSelector } from 'zustand/middleware';
import { create } from 'zustand';
import { type Model } from '@coze-arch/bot-api/developer_api';
import { getModelById } from '../utils/model/get-model-by-id';
import { type ModelPresetValues } from './type';
import { getModelPresetValues } from './helpers/get-model-preset-values';
export interface ModelState {
// 当前环境所有合法的模型列表
onlineModelList: Model[];
/* 不属于当前环境的特殊模型 key === modelId
* 例如: 在 cn-inhouse 选择 GPT 模型, 然后切换到 cn-release, 当前 bot 模型列表 = 正常模型列表 + 1个特殊模型(GPT)
* MultiAgent 模式下, 每个 Agent 模型列表 = 正常模型列表 + 1个特殊模型(可能存在)
* 从特殊模型切换到正常模型后, 不被允许切换回特殊模型
*/
offlineModelMap: Record<string, Model>;
// 纯计算属性, 由 specialModel 和 baseModel 计算而来 key === modelId
// key === modelId
modelPresetValuesMap: Record<string, ModelPresetValues>;
}
export interface ModelAction {
setOnlineModelList: (modelList: Model[]) => void;
setOfflineModelMap: (map: Record<string, Model>) => void;
getModelById: (id: string) => Model | undefined;
setModelPresetValuesMap: (map: Record<string, ModelPresetValues>) => void;
getModelPreset: (id: string) => ModelPresetValues | undefined;
}
export const createModelStore = () => {
const store = create<ModelState & ModelAction>()(
devtools(
subscribeWithSelector((set, get) => ({
onlineModelList: [],
offlineModelMap: {},
modelPresetValuesMap: {},
setOnlineModelList: onlineModelList =>
set({ onlineModelList }, false, 'setOnlineModelList'),
setOfflineModelMap: map =>
set({ offlineModelMap: map }, false, 'setOfflineModelMap'),
getModelById: id => {
const { onlineModelList, offlineModelMap } = get();
return getModelById({ onlineModelList, offlineModelMap, id });
},
setModelPresetValuesMap: map => {
set({ modelPresetValuesMap: map }, false, 'setModelPresetValuesMap');
},
getModelPreset: id => get().modelPresetValuesMap[id],
})),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botEditor.model',
},
),
);
const unSubscribe = store.subscribe(
state => ({
onlineModelList: state.onlineModelList,
offlineModelMap: state.offlineModelMap,
}),
({ onlineModelList, offlineModelMap }) => {
const presetValuesMap: Record<string, ModelPresetValues> = {};
onlineModelList.forEach(model => {
const { model_params } = model;
if (!model_params?.length) {
return;
}
const modelId = String(model.model_type);
presetValuesMap[modelId] = getModelPresetValues({ model_params });
});
Object.keys(offlineModelMap).forEach(modelId => {
const modelParams = offlineModelMap[modelId]?.model_params;
if (!modelParams?.length) {
return;
}
presetValuesMap[modelId] = getModelPresetValues({
model_params: modelParams,
});
});
store.getState().setModelPresetValuesMap(presetValuesMap);
},
{
equalityFn: shallow,
},
);
return { useModelStore: store, unSubscribe };
};
export type ModelStore = ReturnType<typeof createModelStore>['useModelStore'];

View File

@@ -0,0 +1,58 @@
/*
* 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 { type NLPromptModalPosition } from './type';
export interface NLPromptModalState {
visible: boolean;
position: NLPromptModalPosition;
}
export interface NLPromptModalAction {
setVisible: (visible: boolean) => void;
updatePosition: (
updateFn: (position: NLPromptModalPosition) => NLPromptModalPosition,
) => void;
}
export const createNLPromptModalStore = () =>
create<NLPromptModalState & NLPromptModalAction>()(
devtools(
(set, get) => ({
visible: false,
position: {
left: 0,
top: 0,
right: 0,
bottom: 0,
},
setVisible: visible => set({ visible }, false, 'setVisible'),
updatePosition: updateFn => {
const { position } = get();
set({ position: updateFn(position) }, false, 'updatePosition');
},
}),
{
enabled: IS_DEV_MODE,
name: 'botStudio.botEditor.NLPromptModal',
},
),
);
export type NLPromptModalStore = ReturnType<typeof createNLPromptModalStore>;

View File

@@ -0,0 +1,107 @@
/*
* 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 { recordExhaustiveCheck } from '../utils/exhaustive-check';
import { type BotEditorOnboardingSuggestion } from './type';
export interface OnboardingDirtyLogicCompatibilityState {
shuffledSuggestions: BotEditorOnboardingSuggestion[];
}
export interface OnboardingDirtyLogicCompatibilityAction {
setShuffledSuggestions: (
suggestions: BotEditorOnboardingSuggestion[],
) => void;
addShuffledSuggestions: (
suggestions: BotEditorOnboardingSuggestion[],
) => void;
deleteShuffledSuggestionByIdList: (idList: string[]) => void;
updateShuffledSuggestion: (suggestion: BotEditorOnboardingSuggestion) => void;
}
/**
* 用来处理 bot 编辑页 onboarding 的复杂、脏业务逻辑
*/
export const createOnboardingDirtyLogicCompatibilityStore = () =>
create<
OnboardingDirtyLogicCompatibilityState &
OnboardingDirtyLogicCompatibilityAction
>()(
devtools(
subscribeWithSelector((set, get) => ({
shuffledSuggestions: [],
setShuffledSuggestions: suggestions => {
set(
{
shuffledSuggestions: suggestions,
},
false,
'setShuffledSuggestions',
);
},
addShuffledSuggestions: suggestions => {
set(
{
shuffledSuggestions:
get().shuffledSuggestions.concat(suggestions),
},
false,
'addShuffledSuggestions',
);
},
deleteShuffledSuggestionByIdList: idList => {
set(
{
shuffledSuggestions: get().shuffledSuggestions.filter(
suggestion => !idList.find(id => id === suggestion.id),
),
},
false,
'deleteShuffledSuggestionByIdList',
);
},
updateShuffledSuggestion: ({ id, content, highlight, ...rest }) => {
set(
produce<OnboardingDirtyLogicCompatibilityState>(state => {
recordExhaustiveCheck(rest);
const target = state.shuffledSuggestions.find(
item => item.id === id,
);
if (!target) {
return;
}
target.content = content;
target.highlight = highlight;
}),
false,
'updateShuffledSuggestion',
);
},
})),
{
name: 'botStudio.botEditor.onboardingDirtyLogicCompatibility',
enabled: IS_DEV_MODE,
},
),
);
export type OnboardingDirtyLogicCompatibilityStore = ReturnType<
typeof createOnboardingDirtyLogicCompatibilityStore
>;

View File

@@ -0,0 +1,30 @@
/*
* 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 ExtendOnboardingContent } from '@coze-studio/bot-detail-store';
export type BotEditorOnboardingSuggestion =
ExtendOnboardingContent['suggested_questions'][number];
export interface ModelPresetValues {
defaultValues: Record<string, unknown>;
creative?: Record<string, unknown>;
balance?: Record<string, unknown>;
precise?: Record<string, unknown>;
}
export interface NLPromptModalPosition {
top: number;
left: number;
}

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,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 const primitiveExhaustiveCheck = (_: never) => 0;
export const recordExhaustiveCheck = (_: Record<string, never>) => 0;

View File

@@ -0,0 +1,102 @@
/*
* 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,
ModelFuncConfigType,
ModelFuncConfigStatus,
} from '@coze-arch/bot-api/developer_api';
export type ModelCapabilityConfig = {
[key in ModelFuncConfigType]: [
configStatus: ModelFuncConfigStatus,
modelName: string,
];
};
export type TGetModelCapabilityConfig = (params: {
modelIds: string[];
getModelById: (id: string) => Model | undefined;
}) => ModelCapabilityConfig;
// 模型能力配置的 fallback没有配置的能力按支持处理
export const defaultModelCapConfig = Object.values(ModelFuncConfigType).reduce(
(res, type) => ({
...res,
[type]: [
ModelFuncConfigStatus.FullSupport,
'',
] satisfies ModelCapabilityConfig[ModelFuncConfigType],
}),
{},
) as ModelCapabilityConfig;
export const mergeModelFuncConfigStatus = (
...values: ModelFuncConfigStatus[]
) => Math.max(...values);
const mergeModelCapabilityConfig = (
src: ModelCapabilityConfig,
target: Model['func_config'],
targetName: string,
) =>
target
? Object.entries(target).reduce<ModelCapabilityConfig>(
(merged, [key, status]) => {
// 未配置的能力视为完全支持
const [preStatus, preName] = merged[
key as unknown as ModelFuncConfigType
] ?? [ModelFuncConfigStatus.FullSupport, []];
const mergedStatus = mergeModelFuncConfigStatus(preStatus, status);
return {
...merged,
[key]: [
mergedStatus,
mergedStatus === preStatus ? preName : targetName,
],
};
},
src,
)
: src;
export const getMultiAgentModelCapabilityConfig: TGetModelCapabilityConfig = ({
getModelById,
modelIds,
}) =>
Array.from(modelIds).reduce((res, modelId) => {
const model = getModelById(modelId);
if (model?.func_config) {
return mergeModelCapabilityConfig(
res,
model.func_config,
model.name ?? '',
);
}
return res;
}, defaultModelCapConfig);
export const getSingleAgentModelCapabilityConfig: TGetModelCapabilityConfig = ({
getModelById,
modelIds,
}) => {
const model = getModelById(modelIds.at(0) ?? '');
return mergeModelCapabilityConfig(
defaultModelCapConfig,
model?.func_config,
model?.name ?? '',
);
};

View File

@@ -0,0 +1,47 @@
/*
* 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 { ModelParamType } from '@coze-arch/bot-api/developer_api';
import { primitiveExhaustiveCheck } from '../exhaustive-check';
export interface ConvertedModelValueTypeMap {
[ModelParamType.Boolean]: boolean;
[ModelParamType.Float]: number;
[ModelParamType.Int]: number;
[ModelParamType.String]: string;
}
export function convertModelValueType(
value: string,
type: ModelParamType,
): ConvertedModelValueTypeMap[ModelParamType] {
if (type === ModelParamType.Boolean) {
return value === 'true';
}
if (type === ModelParamType.String) {
return value;
}
if (type === ModelParamType.Float || type === ModelParamType.Int) {
return Number(value);
}
// 理论上不走这里
primitiveExhaustiveCheck(type);
return value;
}

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 { type Model } from '@coze-arch/bot-api/developer_api';
export const getModelById = ({
onlineModelList,
offlineModelMap,
id,
}: {
onlineModelList: Model[];
offlineModelMap: Record<string, Model>;
id: string;
}) => {
if (!id) {
return;
}
const expectSpecialModel = offlineModelMap[id];
if (expectSpecialModel) {
return expectSpecialModel;
}
return onlineModelList.find(model => String(model.model_type) === id);
};

View File

@@ -0,0 +1,36 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"compilerOptions": {
"types": [],
"strictNullChecks": true,
"noImplicitAny": true,
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.build.tsbuildinfo"
},
"include": ["src"],
"references": [
{
"path": "../../arch/bot-api/tsconfig.build.json"
},
{
"path": "../../arch/bot-typings/tsconfig.build.json"
},
{
"path": "../../../config/eslint-config/tsconfig.build.json"
},
{
"path": "../../../config/stylelint-config/tsconfig.build.json"
},
{
"path": "../../../config/ts-config/tsconfig.build.json"
},
{
"path": "../../../config/vitest-config/tsconfig.build.json"
},
{
"path": "../../studio/stores/bot-detail/tsconfig.build.json"
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"composite": true
},
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.misc.json"
}
],
"exclude": ["**/*"]
}

View File

@@ -0,0 +1,18 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"include": ["__tests__", "stories", "vitest.config.ts", "tailwind.config.ts"],
"exclude": ["./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
],
"compilerOptions": {
"rootDir": "./",
"outDir": "./dist",
"types": ["vitest/globals"],
"strictNullChecks": true,
"noImplicitAny": true
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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 { defineConfig } from '@coze-arch/vitest-config';
export default defineConfig({
dirname: __dirname,
preset: 'web',
});