add app option (#5911)

This commit is contained in:
heheer
2025-11-13 16:49:19 +08:00
committed by GitHub
parent a838a5862b
commit d4810994db
7 changed files with 416 additions and 127 deletions

View File

@@ -25,6 +25,7 @@ import MyBox from '../../../../MyBox';
import { useMount } from 'ahooks';
import { useRequest2 } from '../../../../../../hooks/useRequest';
import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { useTranslation } from 'next-i18next';
export type SkillOptionItemType = {
description?: string;
@@ -32,7 +33,7 @@ export type SkillOptionItemType = {
onSelect?: (id: string) => Promise<SkillOptionItemType | undefined>;
onClick?: (id: string) => Promise<string | undefined>;
onFolderLoad?: (id: string) => Promise<SkillOptionItemType | undefined>;
onFolderLoad?: (id: string) => Promise<SkillItemType[] | undefined>;
};
export type SkillItemType = {
@@ -41,7 +42,8 @@ export type SkillItemType = {
label: string;
icon?: string;
showArrow?: boolean;
isFolder?: boolean;
canOpen?: boolean;
canUse?: boolean;
open?: boolean;
children?: SkillOptionItemType;
folderChildren?: SkillItemType[];
@@ -54,6 +56,7 @@ export default function SkillPickerPlugin({
skillOption: SkillOptionItemType;
isFocus: boolean;
}) {
const { t } = useTranslation();
const [skillOptions, setSkillOptions] = useState<SkillOptionItemType[]>([skillOption]);
const [isMenuOpen, setIsMenuOpen] = useState(false);
@@ -71,6 +74,8 @@ export default function SkillPickerPlugin({
});
const [currentColumnIndex, setCurrentColumnIndex] = useState<number>(0);
const [currentRowIndex, setCurrentRowIndex] = useState<number>(0);
const [interactionMode, setInteractionMode] = useState<'mouse' | 'keyboard'>('mouse');
const [loadingFolderIds, setLoadingFolderIds] = useState(new Set());
// Refs for scroll management
const itemRefs = useRef<Map<string, HTMLDivElement>>(new Map());
@@ -80,11 +85,18 @@ export default function SkillPickerPlugin({
const itemKey = `${columnIndex}-${rowIndex}`;
const itemElement = itemRefs.current.get(itemKey);
if (itemElement) {
itemElement.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'nearest'
});
if (rowIndex === 0) {
const container = itemElement.parentElement;
if (container) {
container.scrollTop = 0;
}
} else {
itemElement.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'nearest'
});
}
} else if (retryCount < 5) {
// Retry if element not found yet (DOM not ready)
setTimeout(() => {
@@ -97,6 +109,28 @@ export default function SkillPickerPlugin({
minLength: 0
});
// Recursively collects all visible items including expanded folder children for keyboard navigation
const getFlattenedVisibleItems = useCallback(
(columnIndex: number): SkillItemType[] => {
const column = skillOptions[columnIndex];
const flatten = (items: SkillItemType[]): SkillItemType[] => {
const result: SkillItemType[] = [];
items.forEach((item) => {
result.push(item);
// Include folder children only if folder is expanded
if (item.canOpen && item.open && item.folderChildren) {
result.push(...flatten(item.folderChildren));
}
});
return result;
};
return flatten(column.list);
},
[skillOptions]
);
// Handle item selection (hover/keyboard navigation)
const { runAsync: handleItemSelect, loading: isItemSelectLoading } = useRequest2(
async ({
@@ -179,9 +213,87 @@ export default function SkillPickerPlugin({
);
// Handle folder toggle
const handleFolderToggle = useCallback(
(folderId: string, columnIndex: number, item?: SkillItemType) => {},
[]
const { runAsync: handleFolderToggle, loading: isFolderLoading } = useRequest2(
async ({
currentColumnIndex,
item,
option
}: {
currentColumnIndex: number;
item?: SkillItemType;
option?: SkillOptionItemType;
}) => {
if (!item || !item.canOpen) return;
const currentFolder = item;
// Step 1: Toggle folder open/closed state
setSkillOptions((prev) => {
const newOptions = [...prev];
const columnData = { ...newOptions[currentColumnIndex] };
// Recursively find and toggle the target folder
const toggleFolderOpen = (items: SkillItemType[]): SkillItemType[] => {
return items.map((item) => {
// Found the target folder, toggle its open state
if (item.id === currentFolder.id) {
return { ...item, open: !currentFolder.open };
}
// Recursively search in nested folders
if (item.folderChildren) {
return { ...item, folderChildren: toggleFolderOpen(item.folderChildren) };
}
return item;
});
};
columnData.list = toggleFolderOpen(columnData.list);
newOptions[currentColumnIndex] = columnData;
return newOptions;
});
// Step 2: Load folder children only if folder has no data
if (!currentFolder.open && currentFolder?.folderChildren === undefined) {
setLoadingFolderIds((prev) => {
const next = new Set(prev);
next.add(currentFolder.id);
return next;
});
try {
const result = await option?.onFolderLoad?.(currentFolder.id);
setSkillOptions((prev) => {
const newOptions = [...prev];
const columnData = { ...newOptions[currentColumnIndex] };
const addFolderChildren = (items: SkillItemType[]): SkillItemType[] => {
return items.map((item) => {
if (item.id === currentFolder.id) {
return {
...item,
folderChildren: result || []
};
}
if (item.folderChildren) {
return { ...item, folderChildren: addFolderChildren(item.folderChildren) };
}
return item;
});
};
columnData.list = addFolderChildren(columnData.list);
newOptions[currentColumnIndex] = columnData;
return newOptions;
});
} finally {
setLoadingFolderIds((prev) => {
const next = new Set(prev);
next.delete(currentFolder.id);
return next;
});
}
}
}
);
// First init
@@ -210,8 +322,10 @@ export default function SkillPickerPlugin({
e.preventDefault();
e.stopPropagation();
setInteractionMode('keyboard');
if (currentColumnIndex >= 0 && currentColumnIndex < skillOptions.length) {
const columnItems = skillOptions[currentColumnIndex]?.list;
const columnItems = getFlattenedVisibleItems(currentColumnIndex);
if (!columnItems || columnItems.length === 0) return true;
// Use functional update to get the latest row index
@@ -246,8 +360,10 @@ export default function SkillPickerPlugin({
e.preventDefault();
e.stopPropagation();
setInteractionMode('keyboard');
if (currentColumnIndex >= 0 && currentColumnIndex < skillOptions.length) {
const columnItems = skillOptions[currentColumnIndex]?.list;
const columnItems = getFlattenedVisibleItems(currentColumnIndex);
if (!columnItems || columnItems.length === 0) return true;
// Use functional update to get the latest row index
@@ -282,6 +398,8 @@ export default function SkillPickerPlugin({
e.preventDefault();
e.stopPropagation();
setInteractionMode('keyboard');
// Use functional updates to get the latest state
setCurrentColumnIndex((prevColumnIndex) => {
if (prevColumnIndex >= skillOptions.length - 1) return prevColumnIndex;
@@ -327,6 +445,8 @@ export default function SkillPickerPlugin({
e.preventDefault();
e.stopPropagation();
setInteractionMode('keyboard');
// Use functional updates to get the latest state
setCurrentColumnIndex((prevColumnIndex) => {
if (prevColumnIndex <= 0) return prevColumnIndex;
@@ -364,14 +484,20 @@ export default function SkillPickerPlugin({
(e: KeyboardEvent) => {
if (!isMenuOpen) return true;
// Use the latest values from closure to avoid stale state
const latestOption = skillOptions[currentColumnIndex];
const latestItem = latestOption?.list[currentRowIndex];
setInteractionMode('keyboard');
if (latestItem?.isFolder) {
const flattenedItems = getFlattenedVisibleItems(currentColumnIndex);
const latestItem = flattenedItems[currentRowIndex];
const latestOption = skillOptions[currentColumnIndex];
if (latestItem?.canOpen && !(latestItem.open && latestItem.folderChildren?.length === 0)) {
e.preventDefault();
e.stopPropagation();
handleFolderToggle(latestItem.id, currentColumnIndex, latestItem);
handleFolderToggle({
currentColumnIndex,
item: latestItem,
option: latestOption
});
return true;
}
@@ -385,11 +511,13 @@ export default function SkillPickerPlugin({
(e: KeyboardEvent) => {
if (!isMenuOpen) return true;
// Use the latest values from closure to avoid stale state
const latestOption = skillOptions[currentColumnIndex];
const latestItem = latestOption?.list[currentRowIndex];
setInteractionMode('keyboard');
if (latestItem && latestOption) {
const flattenedItems = getFlattenedVisibleItems(currentColumnIndex);
const latestItem = flattenedItems[currentRowIndex];
const latestOption = skillOptions[currentColumnIndex];
if (latestItem?.canUse && latestOption) {
e.preventDefault();
e.stopPropagation();
handleItemClick({ item: latestItem, option: latestOption });
@@ -421,13 +549,164 @@ export default function SkillPickerPlugin({
handleFolderToggle,
handleItemClick,
selectedRowIndex,
scrollIntoView
scrollIntoView,
getFlattenedVisibleItems
]);
// Recursively render item list
const renderItemList = useCallback(
(
items: SkillItemType[],
columnData: SkillOptionItemType,
columnIndex: number,
depth: number = 0,
startFlatIndex: number = 0
): { elements: JSX.Element[]; nextFlatIndex: number } => {
const result: JSX.Element[] = [];
const activeRowIndex = selectedRowIndex[columnIndex];
let currentFlatIndex = startFlatIndex;
console.log('items', { selectedRowIndex, columnIndex, activeRowIndex });
items.forEach((item) => {
const flatIndex = currentFlatIndex;
currentFlatIndex++;
// 前面的列,才有激活态
const isActive = columnIndex < currentColumnIndex && flatIndex === activeRowIndex;
// 当前选中的东西
const isSelected = columnIndex === currentColumnIndex && flatIndex === currentRowIndex;
result.push(
<MyBox
key={item.id}
ref={(el) => {
if (el) {
itemRefs.current.set(`${columnIndex}-${flatIndex}`, el as HTMLDivElement);
} else {
itemRefs.current.delete(`${columnIndex}-${flatIndex}`);
}
}}
px={2}
py={1.5}
gap={2}
pl={1 + depth * 4}
borderRadius={'4px'}
cursor={'pointer'}
bg={isActive || isSelected ? 'myGray.100' : ''}
color={isSelected ? 'primary.700' : 'myGray.600'}
display={'flex'}
alignItems={'center'}
isLoading={loadingFolderIds.has(item.id)}
size={'sm'}
onMouseDown={(e) => {
e.preventDefault();
}}
onMouseMove={(e) => {
if (interactionMode === 'keyboard') {
setInteractionMode('mouse');
}
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (item.canOpen) {
handleFolderToggle({
currentColumnIndex: columnIndex,
item,
option: columnData
});
} else if (item.canUse) {
handleItemClick({
item,
option: columnData
});
}
}}
onMouseEnter={(e) => {
e.preventDefault();
// Ignore mouse hover in keyboard mode
if (interactionMode === 'keyboard') {
return;
}
if (columnIndex !== currentColumnIndex) {
setSelectedRowIndex((state) => ({
...state,
[currentColumnIndex]: currentRowIndex
}));
}
setCurrentRowIndex(flatIndex);
setCurrentColumnIndex(columnIndex);
if (item.canUse) {
handleItemSelect({
currentColumnIndex: columnIndex,
item,
option: columnData
});
}
}}
>
{item.canOpen && !(item.open && item.folderChildren?.length === 0) ? (
<MyIcon
name={'core/chat/chevronRight'}
w={4}
color={'myGray.500'}
transform={item.open ? 'rotate(90deg)' : 'none'}
transition={'transform 0.2s'}
mr={-1}
/>
) : columnData.onFolderLoad ? (
<Box w={3} flexShrink={0} />
) : null}
{item.icon && <Avatar src={item.icon} w={'1.2rem'} borderRadius={'xs'} />}
<Box fontSize={'sm'} fontWeight={'medium'} flex={1}>
{item.label}
{item.canOpen && item.open && item.folderChildren?.length === 0 && (
<Box as="span" color={'myGray.400'} fontSize={'xs'} ml={2}>
{t('app:empty_folder')}
</Box>
)}
</Box>
{item.showArrow && (
<MyIcon name={'core/chat/chevronRight'} w={'0.8rem'} color={'myGray.400'} />
)}
</MyBox>
);
// render folderChildren
if (item.canOpen && item.open && !!item.folderChildren && item.folderChildren.length > 0) {
const { elements, nextFlatIndex } = renderItemList(
item.folderChildren,
columnData,
columnIndex,
depth + 1,
currentFlatIndex
);
result.push(...elements);
currentFlatIndex = nextFlatIndex;
}
});
return { elements: result, nextFlatIndex: currentFlatIndex };
},
[
selectedRowIndex,
currentColumnIndex,
currentRowIndex,
handleFolderToggle,
handleItemClick,
handleItemSelect,
interactionMode,
loadingFolderIds
]
);
// Render single column
const renderColumn = useCallback(
(columnData: SkillOptionItemType, columnIndex: number) => {
const activeRowIndex = selectedRowIndex[columnIndex]; // Active item in this column
const columnWidth = columnData.onFolderLoad ? '280px' : '200px';
return (
<MyBox
@@ -436,7 +715,7 @@ export default function SkillPickerPlugin({
ml={columnIndex > 0 ? 2 : 0}
p={1.5}
borderRadius={'sm'}
w={'200px'}
w={columnWidth}
boxShadow={'0 4px 10px 0 rgba(19, 51, 107, 0.10), 0 0 1px 0 rgba(19, 51, 107, 0.10)'}
bg={'white'}
flexShrink={0}
@@ -448,89 +727,11 @@ export default function SkillPickerPlugin({
{columnData.description}
</Box>
)}
{columnData.list.map((item, index) => {
// 前面的列,才有激活态
const isActive = columnIndex < currentColumnIndex && index === activeRowIndex;
// 当前选中的东西
const isSelected = columnIndex === currentColumnIndex && index === currentRowIndex;
return (
<Flex
key={item.id}
ref={(el) => {
if (el) {
itemRefs.current.set(`${columnIndex}-${index}`, el);
} else {
itemRefs.current.delete(`${columnIndex}-${index}`);
}
}}
px={2}
py={1.5}
gap={2}
pl={2}
borderRadius={'4px'}
cursor={'pointer'}
bg={isActive || isSelected ? 'myGray.100' : ''}
color={isSelected ? 'primary.700' : 'myGray.600'}
onMouseDown={(e) => {
e.preventDefault();
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (item.isFolder) {
handleFolderToggle(item.id, columnIndex, item);
} else {
handleItemClick({
item,
option: columnData
});
}
}}
onMouseEnter={(e) => {
e.preventDefault();
setCurrentRowIndex(index);
setCurrentColumnIndex(columnIndex);
if (!item.isFolder) {
handleItemSelect({
currentColumnIndex: columnIndex,
item,
option: columnData
});
}
}}
>
{/* Folder expand/collapse icon */}
{item.isFolder && (
<MyIcon
name={'core/chat/chevronRight'}
w={'12px'}
color={'myGray.400'}
// transform={foldedIds.has(item.id) ? 'rotate(90deg)' : 'none'}
/>
)}
{item.icon && <Avatar src={item.icon} w={'1.2rem'} borderRadius={'xs'} />}
<Box fontSize={'sm'} fontWeight={'medium'} flex={1}>
{item.label}
</Box>
{item.showArrow && (
<MyIcon name={'core/chat/chevronRight'} w={'0.8rem'} color={'myGray.400'} />
)}
</Flex>
);
})}
{renderItemList(columnData.list, columnData, columnIndex).elements}
</MyBox>
);
},
[
selectedRowIndex,
currentColumnIndex,
isItemClickLoading,
currentRowIndex,
handleFolderToggle,
handleItemClick,
handleItemSelect
]
[currentColumnIndex, isItemClickLoading, renderItemList]
);
// For LexicalTypeaheadMenuPlugin compatibility

View File

@@ -58,8 +58,8 @@
"auto_execute_default_prompt_placeholder": "Default questions sent when executing automatically",
"auto_execute_tip": "After turning it on, the workflow will be automatically triggered when the user enters the conversation interface. \nExecution order: 1. Dialogue starter; 2. Global variables; 3. Automatic execution.",
"auto_save": "Auto save",
"change_app_type": "Change App Type",
"can_select_toolset": "Entire toolset available for selection",
"change_app_type": "Change App Type",
"chat_debug": "Chat Preview",
"chat_logs": "Logs",
"chat_logs_tips": "Logs will record the online, shared, and API (requires chatId) conversation records of this app.",
@@ -136,6 +136,7 @@
"document_upload": "Document Upload",
"edit_app": "Application details",
"edit_info": "Edit",
"empty_folder": "(empty folder)",
"empty_tool_tips": "Please add tools on the left side",
"execute_time": "Execution Time",
"expand_tool_create": "Expand MCP/Http create",
@@ -305,6 +306,7 @@
"show_top_p_tip": "An alternative method of temperature sampling, called Nucleus sampling, the model considers the results of tokens with TOP_P probability mass quality. \nTherefore, 0.1 means that only tokens containing the highest probability quality are considered. \nThe default is 1.",
"simple_tool_tips": "This tool contains special inputs and does not support being called by simple applications.",
"source_updateTime": "Update time",
"space_to_expand_folder": "Press \"Space\" to expand the folder",
"stop_sign": "Stop",
"stop_sign_placeholder": "Multiple serial numbers are separated by |, for example: aaa|stop",
"stream_response": "Stream",

View File

@@ -60,8 +60,8 @@
"auto_execute_default_prompt_placeholder": "自动执行时,发送的默认问题",
"auto_execute_tip": "开启后用户进入对话界面将自动触发工作流。执行顺序1、对话开场白2、全局变量3、自动执行。",
"auto_save": "自动保存",
"change_app_type": "更改应用类型",
"can_select_toolset": "可选择整个工具集",
"change_app_type": "更改应用类型",
"chat_debug": "调试预览",
"chat_logs": "对话日志",
"chat_logs_tips": "日志会记录该应用的在线、分享和 API需填写 chatId对话记录",
@@ -140,6 +140,7 @@
"edit_app": "应用详情",
"edit_info": "编辑信息",
"edit_param": "编辑参数",
"empty_folder": "(空文件夹)",
"empty_tool_tips": "请在左侧添加工具",
"execute_time": "执行时间",
"expand_tool_create": "展开MCP、Http创建",
@@ -318,6 +319,7 @@
"show_top_p_tip": "用温度采样的替代方法称为Nucleus采样该模型考虑了具有TOP_P概率质量质量的令牌的结果。因此0.1表示仅考虑包含最高概率质量的令牌。默认为 1。",
"simple_tool_tips": "该工具含有特殊输入,暂不支持被简易应用调用",
"source_updateTime": "更新时间",
"space_to_expand_folder": "按\"空格\"展开文件夹",
"stop_sign": "停止序列",
"stop_sign_placeholder": "多个序列号通过 | 隔开例如aaa|stop",
"stream_response": "流输出",

View File

@@ -58,8 +58,8 @@
"auto_execute_default_prompt_placeholder": "自動執行時,傳送的預設問題",
"auto_execute_tip": "開啟後,使用者進入對話式介面將自動觸發工作流程。\n執行順序1、對話開場白2、全域變數3、自動執行。",
"auto_save": "自動儲存",
"change_app_type": "更改應用程式類型",
"can_select_toolset": "可選擇整個工具集",
"change_app_type": "更改應用程式類型",
"chat_debug": "聊天預覽",
"chat_logs": "對話紀錄",
"chat_logs_tips": "紀錄會記錄此應用程式的線上、分享和 API需填寫 chatId對話紀錄",
@@ -135,6 +135,7 @@
"document_upload": "文件上傳",
"edit_app": "應用詳情",
"edit_info": "編輯資訊",
"empty_folder": "(空文件夾)",
"empty_tool_tips": "請在左側添加工具",
"execute_time": "執行時間",
"expand_tool_create": "展開 MCP、Http 創建",
@@ -304,6 +305,7 @@
"show_top_p_tip": "用溫度取樣的替代方法,稱為 Nucleus 取樣,該模型考慮了具有 TOP_P 機率質量質量的令牌的結果。\n因此0.1 表示僅考慮包含最高機率質量的令牌。\n預設為 1。",
"simple_tool_tips": "該工具含有特殊輸入,暫不支持被簡易應用調用",
"source_updateTime": "更新時間",
"space_to_expand_folder": "按\"空格\"展開文件夾",
"stop_sign": "停止序列",
"stop_sign_placeholder": "多個序列號透過 | 隔開例如aaa|stop",
"stream_response": "流輸出",

View File

@@ -19,7 +19,12 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
import type { SkillLabelItemType } from '@fastgpt/web/components/common/Textarea/PromptEditor/plugins/SkillLabelPlugin';
import dynamic from 'next/dynamic';
import type { AppFormEditFormType } from '@fastgpt/global/core/app/type';
import { getAppToolTemplates, getToolPreviewNode } from '@/web/core/app/api/tool';
import {
getAppToolTemplates,
getToolPreviewNode,
getTeamAppTemplates
} from '@/web/core/app/api/tool';
import { AppTypeEnum, AppTypeList, ToolTypeList } from '@fastgpt/global/core/app/constants';
const ConfigToolModal = dynamic(() => import('../../component/ConfigToolModal'));
@@ -55,13 +60,15 @@ export const useSkillManager = ({
const data = await getAppToolTemplates({ getAll: true }).catch((err) => {
return [];
});
return data.map<SkillItemType>((item) => ({
id: item.id,
parentId: item.parentId,
label: item.name,
icon: item.avatar,
showArrow: item.isFolder
}));
return data.map<SkillItemType>((item) => {
return {
id: item.id,
parentId: item.parentId,
label: item.name,
icon: item.avatar,
showArrow: item.isFolder
};
});
},
{
manual: false
@@ -76,8 +83,62 @@ export const useSkillManager = ({
[systemTools]
);
/* ===== Workflow tool ===== */
/* ===== Team Apps ===== */
const { data: allTeamApps = [] } = useRequest2(
async () => {
return await getTeamAppTemplates({ parentId: null });
},
{
manual: false
}
);
const myTools = useMemo(
() =>
allTeamApps
.filter((item) => [AppTypeEnum.toolFolder, ...ToolTypeList].includes(item.appType))
.map((item) => ({
id: item.id,
label: item.name,
icon: item.avatar,
canOpen: item.isFolder ?? false,
canUse: item.appType !== AppTypeEnum.folder && item.appType !== AppTypeEnum.toolFolder
})),
[allTeamApps]
);
const agentApps = useMemo(
() =>
allTeamApps
.filter((item) => [AppTypeEnum.folder, ...AppTypeList].includes(item.appType))
.map((item) => ({
id: item.id,
label: item.name,
icon: item.avatar,
canOpen: item.isFolder ?? false,
canUse: item.appType !== AppTypeEnum.folder && item.appType !== AppTypeEnum.toolFolder
})),
[allTeamApps]
);
const onFolderLoadTeamApps = useCallback(async (folderId: string, types: AppTypeEnum[]) => {
const children = await getTeamAppTemplates({ parentId: folderId, type: types });
if (!children || children.length === 0) {
return [];
}
return children.map<SkillItemType>((item) => {
return {
parentId: folderId,
id: item.id,
label: item.name,
icon: item.avatar,
canOpen: item.isFolder ?? false,
canUse: item.appType !== AppTypeEnum.folder && item.appType !== AppTypeEnum.toolFolder
};
});
}, []);
/* ===== Workflow tool ===== */
const { runAsync: onAddAppOrTool } = useRequest2(
async (appId: string) => {
const toolTemplate = await getToolPreviewNode({ appId });
@@ -144,7 +205,20 @@ export const useSkillManager = ({
},
onClick: onAddAppOrTool
};
} else if (id === 'app') {
} else if (id === 'myTools') {
return {
description: t('app:space_to_expand_folder'),
list: myTools,
onFolderLoad: (folderId: string) => onFolderLoadTeamApps(folderId, ToolTypeList),
onClick: onAddAppOrTool
};
} else if (id === 'agent') {
return {
description: t('app:space_to_expand_folder'),
list: agentApps,
onFolderLoad: (folderId: string) => onFolderLoadTeamApps(folderId, AppTypeList),
onClick: onAddAppOrTool
};
}
return undefined;
},
@@ -152,16 +226,21 @@ export const useSkillManager = ({
{
id: 'systemTool',
label: t('app:core.module.template.System Tools'),
icon: 'core/workflow/template/toolCall'
},
{
id: 'myTools',
label: t('common:navbar.Tools'),
icon: 'core/app/type/pluginFill'
},
{
id: 'app',
label: t('common:core.module.template.Team app'),
icon: 'core/app/type/simpleFill'
id: 'agent',
label: 'Agent',
icon: 'core/workflow/template/runApp'
}
]
};
}, [onAddAppOrTool, onLoadSystemTool, t]);
}, [onAddAppOrTool, onLoadSystemTool, myTools, agentApps, onFolderLoadTeamApps, t]);
/* ===== Selected skills ===== */
const selectedSkills = useMemoEnhance<SkillLabelItemType[]>(() => {

View File

@@ -10,9 +10,7 @@ const AppTypeTag = ({ type }: { type: AppTypeEnum }) => {
const map = useRef({
[AppTypeEnum.agent]: {
label: 'Agent',
icon: 'core/app/type/simple',
bg: '#DBF3FF',
color: '#0884DD'
icon: 'core/app/type/simple'
},
[AppTypeEnum.simple]: {
label: t('app:type.Chat_Agent'),

View File

@@ -47,7 +47,9 @@ export const getTeamAppTemplates = async (data?: {
...item,
intro: item.description || '',
flowNodeType: FlowNodeTypeEnum.tool,
templateType: FlowNodeTemplateTypeEnum.teamApp
templateType: FlowNodeTemplateTypeEnum.teamApp,
appType: app.type,
isFolder: false
}));
// handle http toolset
} else if (app.type === AppTypeEnum.httpToolSet) {
@@ -59,7 +61,9 @@ export const getTeamAppTemplates = async (data?: {
name: item.name,
intro: item.description || '',
flowNodeType: FlowNodeTypeEnum.tool,
templateType: FlowNodeTemplateTypeEnum.teamApp
templateType: FlowNodeTemplateTypeEnum.teamApp,
appType: app.type,
isFolder: false
}));
}
}
@@ -87,7 +91,8 @@ export const getTeamAppTemplates = async (data?: {
showStatus: false,
version: app.pluginData?.nodeVersion,
isTool: true,
sourceMember: app.sourceMember
sourceMember: app.sourceMember,
appType: app.type
}))
);
};