mirror of
https://github.com/labring/FastGPT.git
synced 2025-12-25 20:02:47 +00:00
skill editor ui
This commit is contained in:
parent
27b8d7a1f0
commit
0ea6d2ee3f
|
|
@ -1 +0,0 @@
|
|||
export type AgentSubAppItemType = {};
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { ObjectIdSchema } from '../../../../global/common/type/mongo';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type AgentSubAppItemType = {};
|
||||
|
||||
/* ===== Dataset ==== */
|
||||
|
|
@ -100,6 +100,19 @@ export type AppDatasetSearchParamsType = {
|
|||
datasetSearchExtensionBg?: string;
|
||||
};
|
||||
|
||||
/* ===== skill ===== */
|
||||
export type SkillEditType = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
prompt: string;
|
||||
dataset: {
|
||||
list: SelectedDatasetType[];
|
||||
};
|
||||
selectedTools: SelectedToolItemType[];
|
||||
fileSelectConfig: AppFileSelectConfigType;
|
||||
};
|
||||
|
||||
export type SelectedToolItemType = FlowNodeTemplateType & {
|
||||
configStatus?: 'active' | 'waitingForConfig' | 'invalid';
|
||||
};
|
||||
|
|
@ -126,6 +139,7 @@ export type AppFormEditFormType = {
|
|||
} & AppDatasetSearchParamsType;
|
||||
selectedTools: SelectedToolItemType[];
|
||||
chatConfig: AppChatConfigType;
|
||||
skills: SkillEditType[];
|
||||
};
|
||||
|
||||
export type HttpToolConfigType = {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ export const getDefaultAppForm = (): AppFormEditFormType => {
|
|||
datasetSearchExtensionBg: ''
|
||||
},
|
||||
selectedTools: [],
|
||||
chatConfig: {}
|
||||
chatConfig: {},
|
||||
skills: []
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import { ChatRoleEnum } from '../constants';
|
|||
import { UserChatItemSchema, SystemChatItemSchema, ToolModuleResponseItemSchema } from '../type';
|
||||
|
||||
export enum HelperBotTypeEnum {
|
||||
topAgent = 'topAgent'
|
||||
topAgent = 'topAgent',
|
||||
skillEditor = 'skillEditor'
|
||||
}
|
||||
export const HelperBotTypeEnumSchema = z.enum(Object.values(HelperBotTypeEnum));
|
||||
export type HelperBotTypeEnumType = z.infer<typeof HelperBotTypeEnumSchema>;
|
||||
|
|
@ -72,7 +73,7 @@ export type HelperBotChatItemSiteType = z.infer<typeof HelperBotChatItemSiteSche
|
|||
|
||||
/* 具体的 bot 的特有参数 */
|
||||
|
||||
// AI 模型配置
|
||||
// Top agent
|
||||
export const topAgentParamsSchema = z.object({
|
||||
role: z.string().nullish(),
|
||||
taskObject: z.string().nullish(),
|
||||
|
|
@ -81,3 +82,7 @@ export const topAgentParamsSchema = z.object({
|
|||
fileUpload: z.boolean().nullish()
|
||||
});
|
||||
export type TopAgentParamsType = z.infer<typeof topAgentParamsSchema>;
|
||||
|
||||
// Skill editor
|
||||
export const skillEditorParamsSchema = z.object({});
|
||||
export type SkillEditorParamsType = z.infer<typeof skillEditorParamsSchema>;
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ export enum NodeInputKeyEnum {
|
|||
|
||||
// agent
|
||||
subApps = 'subApps',
|
||||
skills = 'skills',
|
||||
isAskAgent = 'isAskAgent',
|
||||
isPlanAgent = 'isPlanAgent',
|
||||
isConfirmPlanAgent = 'isConfirmPlanAgent',
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
type HelperBotChatItemSiteType,
|
||||
HelperBotTypeEnum,
|
||||
HelperBotTypeEnumSchema,
|
||||
skillEditorParamsSchema,
|
||||
topAgentParamsSchema
|
||||
} from '../../../../core/chat/helperBot/type';
|
||||
import { z } from 'zod';
|
||||
|
|
@ -58,6 +59,10 @@ export const HelperBotCompletionsParamsSchema = z.object({
|
|||
z.object({
|
||||
type: z.literal(HelperBotTypeEnum.topAgent),
|
||||
data: topAgentParamsSchema
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(HelperBotTypeEnum.skillEditor),
|
||||
data: skillEditorParamsSchema
|
||||
})
|
||||
])
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { HelperBotTypeEnum } from '@fastgpt/global/core/chat/helperBot/type';
|
||||
import { dispatchTopAgent } from './topAgent';
|
||||
import { dispatchSkillEditor } from './skillEditor';
|
||||
|
||||
export const dispatchMap = {
|
||||
[HelperBotTypeEnum.topAgent]: dispatchTopAgent
|
||||
[HelperBotTypeEnum.topAgent]: dispatchTopAgent,
|
||||
[HelperBotTypeEnum.skillEditor]: dispatchSkillEditor
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
import type { HelperBotDispatchParamsType, HelperBotDispatchResponseType } from '../type';
|
||||
|
||||
export const dispatchSkillEditor = async (
|
||||
props: HelperBotDispatchParamsType
|
||||
): Promise<HelperBotDispatchResponseType> => {
|
||||
console.log(props, 22222);
|
||||
return {
|
||||
aiResponse: [],
|
||||
usage: {
|
||||
model: '',
|
||||
inputTokens: 0,
|
||||
outputTokens: 0
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -9,11 +9,12 @@ import { generateResourceList } from './utils';
|
|||
import { TopAgentFormDataSchema } from './type';
|
||||
import { addLog } from '../../../../../common/system/log';
|
||||
import { formatAIResponse } from '../utils';
|
||||
import type { TopAgentParamsType } from '@fastgpt/global/core/chat/helperBot/type';
|
||||
|
||||
export const dispatchTopAgent = async (
|
||||
props: HelperBotDispatchParamsType
|
||||
props: HelperBotDispatchParamsType<TopAgentParamsType>
|
||||
): Promise<HelperBotDispatchResponseType> => {
|
||||
const { query, files, metadata, histories, workflowResponseWrite, user } = props;
|
||||
const { query, files, data, histories, workflowResponseWrite, user } = props;
|
||||
|
||||
const modelData = getLLMModel();
|
||||
if (!modelData) {
|
||||
|
|
@ -32,7 +33,7 @@ export const dispatchTopAgent = async (
|
|||
});
|
||||
const systemPrompt = getPrompt({
|
||||
resourceList,
|
||||
metadata: metadata.data
|
||||
metadata: data
|
||||
});
|
||||
|
||||
const historyMessages = helperChats2GPTMessages({
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { LocaleList } from '@fastgpt/global/common/i18n/type';
|
|||
export const HelperBotDispatchParamsSchema = z.object({
|
||||
query: z.string(),
|
||||
files: HelperBotCompletionsParamsSchema.shape.files,
|
||||
metadata: HelperBotCompletionsParamsSchema.shape.metadata,
|
||||
data: z.unknown(), // Allow any type, will be constrained by generic type parameter
|
||||
histories: z.array(HelperBotChatItemSchema),
|
||||
workflowResponseWrite: WorkflowResponseFnSchema,
|
||||
|
||||
|
|
@ -22,7 +22,14 @@ export const HelperBotDispatchParamsSchema = z.object({
|
|||
lang: z.enum(LocaleList)
|
||||
})
|
||||
});
|
||||
export type HelperBotDispatchParamsType = z.infer<typeof HelperBotDispatchParamsSchema>;
|
||||
|
||||
type BaseHelperBotDispatchParamsType = z.infer<typeof HelperBotDispatchParamsSchema>;
|
||||
export type HelperBotDispatchParamsType<T = unknown> = Omit<
|
||||
BaseHelperBotDispatchParamsType,
|
||||
'data'
|
||||
> & {
|
||||
data: T;
|
||||
};
|
||||
|
||||
export const HelperBotDispatchResponseSchema = z.object({
|
||||
aiResponse: z.array(AIChatItemValueItemSchema),
|
||||
|
|
|
|||
|
|
@ -327,6 +327,12 @@
|
|||
"show_templates": "Expand",
|
||||
"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.",
|
||||
"skill_description_placeholder": "Used to guide the Agent to select the skill for execution",
|
||||
"skill_editor": "Skill-assisted generation",
|
||||
"skill_empty_name": "Unnamed skill",
|
||||
"skill_name_placeholder": "Please enter the skill description, for display only",
|
||||
"skills": "Skills",
|
||||
"skills_tip": "Model behavioral knowledge",
|
||||
"source_updateTime": "Update time",
|
||||
"space_to_expand_folder": "Press \"Space\" to expand the folder",
|
||||
"stop_sign": "Stop",
|
||||
|
|
|
|||
|
|
@ -840,6 +840,7 @@
|
|||
"delete_folder": "Delete Folder",
|
||||
"delete_success": "Deleted Successfully",
|
||||
"delete_warning": "Deletion Warning",
|
||||
"descripton": "describe",
|
||||
"discount_coupon_used": "Coupon used:",
|
||||
"embedding_model_not_config": "No index model is detected",
|
||||
"enable_auth": "Enable authentication",
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@
|
|||
"export_config_successful": "已复制配置,自动过滤部分敏感信息,请注意检查是否仍有敏感数据",
|
||||
"export_configs": "导出配置",
|
||||
"export_log_filename": "{{name}} 对话日志.csv",
|
||||
"failed_tools": "失败的工具",
|
||||
"fastgpt_marketplace": "FastGPT 插件市场",
|
||||
"feedback_count": "用户反馈",
|
||||
"file_quote_link": "文件链接",
|
||||
|
|
@ -340,6 +341,12 @@
|
|||
"show_templates": "显示模板",
|
||||
"show_top_p_tip": "用温度采样的替代方法,称为Nucleus采样,该模型考虑了具有TOP_P概率质量质量的令牌的结果。因此,0.1表示仅考虑包含最高概率质量的令牌。默认为 1。",
|
||||
"simple_tool_tips": "该工具含有特殊输入,暂不支持被简易应用调用",
|
||||
"skill_description_placeholder": "用于引导 Agent 选中该技能进行执行",
|
||||
"skill_editor": "技能辅助生成",
|
||||
"skill_empty_name": "未命名的技能",
|
||||
"skill_name_placeholder": "请输入技能明,仅用于展示",
|
||||
"skills": "技能",
|
||||
"skills_tip": "模型的行为知识",
|
||||
"source_updateTime": "更新时间",
|
||||
"space_to_expand_folder": "按\"空格\"展开文件夹",
|
||||
"stop_sign": "停止序列",
|
||||
|
|
@ -396,6 +403,7 @@
|
|||
"tool_active_system_config_price_desc_folder": "需额外支付密钥价格,依据实际使用工具扣费。",
|
||||
"tool_detail": "工具详情",
|
||||
"tool_input_param_tip": "该工具正常运行需要配置相关信息",
|
||||
"tool_load_failed": "部分工具加载失败",
|
||||
"tool_not_active": "该工具尚未激活",
|
||||
"tool_offset_tips": "该工具已无法使用,将中断应用运行,请立即替换",
|
||||
"tool_param_config": "参数配置",
|
||||
|
|
@ -468,8 +476,6 @@
|
|||
"toolkit_uninstalled": "未安装",
|
||||
"toolkit_update_failed": "更新失败",
|
||||
"toolkit_user_guide": "使用说明",
|
||||
"tool_load_failed": "部分工具加载失败",
|
||||
"failed_tools": "失败的工具",
|
||||
"tools": "工具",
|
||||
"tools_no_description": "这个工具没有介绍~",
|
||||
"tools_tip": "声明模型可用的工具,可以实现与外部系统交互等扩展能力",
|
||||
|
|
|
|||
|
|
@ -845,6 +845,7 @@
|
|||
"delete_folder": "删除文件夹",
|
||||
"delete_success": "删除成功",
|
||||
"delete_warning": "删除警告",
|
||||
"descripton": "描述",
|
||||
"discount_coupon_used": "已使用优惠券:",
|
||||
"embedding_model_not_config": "检测到没有可用的索引模型",
|
||||
"enable_auth": "启用鉴权",
|
||||
|
|
|
|||
|
|
@ -325,6 +325,12 @@
|
|||
"show_templates": "顯示模板",
|
||||
"show_top_p_tip": "用溫度取樣的替代方法,稱為 Nucleus 取樣,該模型考慮了具有 TOP_P 機率質量質量的令牌的結果。\n因此,0.1 表示僅考慮包含最高機率質量的令牌。\n預設為 1。",
|
||||
"simple_tool_tips": "該工具含有特殊輸入,暫不支持被簡易應用調用",
|
||||
"skill_description_placeholder": "用於引導 Agent 選中該技能進行執行",
|
||||
"skill_editor": "技能輔助生成",
|
||||
"skill_empty_name": "未命名的技能",
|
||||
"skill_name_placeholder": "請輸入技能明,僅用於展示",
|
||||
"skills": "技能",
|
||||
"skills_tip": "模型的行為知識",
|
||||
"source_updateTime": "更新時間",
|
||||
"space_to_expand_folder": "按\"空格\"展開文件夾",
|
||||
"stop_sign": "停止序列",
|
||||
|
|
|
|||
|
|
@ -839,6 +839,7 @@
|
|||
"delete_folder": "刪除資料夾",
|
||||
"delete_success": "刪除成功",
|
||||
"delete_warning": "刪除警告",
|
||||
"descripton": "描述",
|
||||
"discount_coupon_used": "已使用優惠券:",
|
||||
"embedding_model_not_config": "偵測到沒有可用的索引模型",
|
||||
"enable_auth": "啟用鑑權",
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ const ChatBox = ({ type, metadata, onApply, ...props }: HelperBotProps) => {
|
|||
name: item.name
|
||||
})),
|
||||
metadata: {
|
||||
type: 'topAgent',
|
||||
type,
|
||||
data: metadata
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
|
||||
import ChatTest from './ChatTest';
|
||||
import AppCard from '../FormComponent/AppCard';
|
||||
import EditForm from './EditForm';
|
||||
import { type AppFormEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import type { SkillEditType, AppFormEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import { cardStyles } from '../../constants';
|
||||
|
||||
import styles from '../FormComponent/styles.module.scss';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { type SimpleAppSnapshotType } from '../FormComponent/useSnapshots';
|
||||
import { agentForm2AppWorkflow } from './utils';
|
||||
import styles from '../FormComponent/styles.module.scss';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const SkillEditForm = dynamic(() => import('./SkillEdit/EditForm'), { ssr: false });
|
||||
const SKillChatTest = dynamic(() => import('./SkillEdit/ChatTest'), { ssr: false });
|
||||
|
||||
const Edit = ({
|
||||
appForm,
|
||||
|
|
@ -23,6 +26,7 @@ const Edit = ({
|
|||
}) => {
|
||||
const { isPc } = useSystem();
|
||||
const [renderEdit, setRenderEdit] = useState(true);
|
||||
const [editSkill, setEditSkill] = useState<SkillEditType>();
|
||||
|
||||
return (
|
||||
<Box
|
||||
|
|
@ -33,7 +37,9 @@ const Edit = ({
|
|||
gap={1}
|
||||
borderRadius={'lg'}
|
||||
overflowY={['auto', 'unset']}
|
||||
position={'relative'}
|
||||
>
|
||||
{/* Top agent editor */}
|
||||
{renderEdit && (
|
||||
<Box
|
||||
className={styles.EditAppBox}
|
||||
|
|
@ -47,7 +53,11 @@ const Edit = ({
|
|||
</Box>
|
||||
|
||||
<Box pb={4}>
|
||||
<EditForm appForm={appForm} setAppForm={setAppForm} />
|
||||
<EditForm
|
||||
appForm={appForm}
|
||||
setAppForm={setAppForm}
|
||||
onEditSkill={(e) => setEditSkill(e)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
|
@ -61,6 +71,52 @@ const Edit = ({
|
|||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Mask */}
|
||||
{editSkill && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
bg={'rgba(0, 0, 0, 0.5)'}
|
||||
borderRadius={'md'}
|
||||
zIndex={9}
|
||||
></Box>
|
||||
)}
|
||||
|
||||
{/* Skill editor */}
|
||||
<Flex
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
bg={'white'}
|
||||
borderRadius={'md'}
|
||||
zIndex={10}
|
||||
transform={editSkill ? 'translateX(0)' : 'translateX(100%)'}
|
||||
transition={'transform 0.3s ease-in-out'}
|
||||
pointerEvents={editSkill ? 'auto' : 'none'}
|
||||
>
|
||||
{editSkill && (
|
||||
<>
|
||||
<Box overflowY={'auto'} minW={['auto', '580px']} flex={'1'} borderRight={'base'}>
|
||||
<SkillEditForm
|
||||
model={appForm.aiSettings.model}
|
||||
fileSelectConfig={appForm.chatConfig.fileSelectConfig}
|
||||
defaultSkill={editSkill}
|
||||
onClose={() => setEditSkill(undefined)}
|
||||
setAppForm={setAppForm}
|
||||
/>
|
||||
</Box>
|
||||
<Box flex={'2 0 0'} w={0} mb={3}>
|
||||
<SKillChatTest skill={editSkill} setAppForm={setAppForm} />
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
HStack,
|
||||
Input
|
||||
} from '@chakra-ui/react';
|
||||
import type { AppFormEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import type { AppFormEditFormType, SkillEditType } from '@fastgpt/global/core/app/type.d';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
|
|
@ -20,23 +20,17 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
|
|||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import VariableEdit from '@/components/core/app/VariableEdit';
|
||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
||||
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
|
||||
import SearchParamsTip from '@/components/core/dataset/SearchParamsTip';
|
||||
import SettingLLMModel from '@/components/core/ai/SettingLLMModel';
|
||||
import { TTSTypeEnum } from '@/web/core/app/constants';
|
||||
import { workflowSystemVariables } from '@/web/core/app/utils';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '@/pageComponents/app/detail/context';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import VariableTip from '@/components/common/Textarea/MyTextarea/VariableTip';
|
||||
import { getWebLLMModel } from '@/web/common/system/utils';
|
||||
import ToolSelect from '../FormComponent/ToolSelector/ToolSelect';
|
||||
import OptimizerPopover from '@/components/common/PromptEditor/OptimizerPopover';
|
||||
import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { type SelectedToolItemType, useSkillManager } from './hooks/useSkillManager';
|
||||
import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance';
|
||||
import SkillRow from './SkillEdit/Row';
|
||||
import { cardStyles } from '../../constants';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
|
||||
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
|
||||
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
|
||||
|
|
@ -56,10 +50,12 @@ const BoxStyles: BoxProps = {
|
|||
|
||||
const EditForm = ({
|
||||
appForm,
|
||||
setAppForm
|
||||
setAppForm,
|
||||
onEditSkill
|
||||
}: {
|
||||
appForm: AppFormEditFormType;
|
||||
setAppForm: React.Dispatch<React.SetStateAction<AppFormEditFormType>>;
|
||||
onEditSkill: (e: SkillEditType) => void;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
|
|
@ -80,26 +76,6 @@ const EditForm = ({
|
|||
onClose: onCloseDatasetParams
|
||||
} = useDisclosure();
|
||||
|
||||
const formatVariables = useMemo(
|
||||
() =>
|
||||
formatEditorVariablePickerIcon([
|
||||
...workflowSystemVariables.filter(
|
||||
(variable) =>
|
||||
!['appId', 'chatId', 'responseChatItemId', 'histories'].includes(variable.key)
|
||||
),
|
||||
...(appForm.chatConfig.variables || [])
|
||||
]).map((item) => ({
|
||||
...item,
|
||||
label: t(item.label as any),
|
||||
parent: {
|
||||
id: 'VARIABLE_NODE_ID',
|
||||
label: t('common:core.module.Variable'),
|
||||
avatar: 'core/workflow/template/variable'
|
||||
}
|
||||
})),
|
||||
[appForm.chatConfig.variables, t]
|
||||
);
|
||||
|
||||
const selectedModel = getWebLLMModel(appForm.aiSettings.model);
|
||||
const tokenLimit = useMemo(() => {
|
||||
return selectedModel?.quoteMaxToken || 3000;
|
||||
|
|
@ -226,9 +202,44 @@ const EditForm = ({
|
|||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box {...BoxStyles}>
|
||||
<SkillRow
|
||||
skills={appForm.skills}
|
||||
onEditSkill={onEditSkill}
|
||||
onDeleteSkill={(id) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
skills: state.skills.filter((item) => item.id !== id)
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* tool choice */}
|
||||
<Box {...BoxStyles}>
|
||||
<ToolSelect appForm={appForm} setAppForm={setAppForm} />
|
||||
<ToolSelect
|
||||
selectedModel={selectedModel}
|
||||
selectedTools={appForm.selectedTools}
|
||||
fileSelectConfig={appForm.chatConfig.fileSelectConfig}
|
||||
onAddTool={(e) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: [e, ...(state.selectedTools || [])]
|
||||
}));
|
||||
}}
|
||||
onUpdateTool={(e) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools:
|
||||
state.selectedTools?.map((item) => (item.id === e.id ? e : item)) || []
|
||||
}));
|
||||
}}
|
||||
onRemoveTool={(id) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: state.selectedTools?.filter((item) => item.id !== id) || []
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* dataset */}
|
||||
|
|
@ -239,17 +250,6 @@ const EditForm = ({
|
|||
<FormLabel ml={2}>{t('app:dataset')}</FormLabel>
|
||||
</Flex>
|
||||
<Button
|
||||
variant={'transparentBase'}
|
||||
leftIcon={<MyIcon name="common/addLight" w={'0.8rem'} />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
fontSize={'sm'}
|
||||
onClick={onOpenKbSelect}
|
||||
>
|
||||
{t('common:Choose')}
|
||||
</Button>
|
||||
<Button
|
||||
mr={'-5px'}
|
||||
variant={'transparentBase'}
|
||||
leftIcon={<MyIcon name={'edit'} w={'14px'} />}
|
||||
iconSpacing={1}
|
||||
|
|
@ -259,6 +259,17 @@ const EditForm = ({
|
|||
>
|
||||
{t('common:Params')}
|
||||
</Button>
|
||||
<Button
|
||||
mr={'-5px'}
|
||||
variant={'transparentBase'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
fontSize={'sm'}
|
||||
onClick={onOpenKbSelect}
|
||||
>
|
||||
{t('common:Choose')}
|
||||
</Button>
|
||||
</Flex>
|
||||
{appForm.dataset.datasets?.length > 0 && (
|
||||
<Box my={3}>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useMemo } from 'react';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import type { AppFormEditFormType, SkillEditType } from '@fastgpt/global/core/app/type';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '../../../context';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { cardStyles } from '../../../constants';
|
||||
import HelperBot from '@/components/core/chat/HelperBot';
|
||||
import { HelperBotTypeEnum } from '@fastgpt/global/core/chat/helperBot/type';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
||||
type Props = {
|
||||
skill: SkillEditType;
|
||||
setAppForm: React.Dispatch<React.SetStateAction<AppFormEditFormType>>;
|
||||
};
|
||||
const ChatTest = ({ skill, setAppForm }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
// 构建 SkillAgent metadata,从 appForm 中提取配置
|
||||
const skillAgentMetadata = useMemo(() => ({}), []);
|
||||
|
||||
return (
|
||||
<MyBox display={'flex'} position={'relative'} flexDirection={'column'} h={'full'} py={4}>
|
||||
<Flex px={[2, 5]} pb={2}>
|
||||
<Box color={'myGray.900'} fontWeight={'bold'} flex={1}>
|
||||
{t('app:skill_editor')}
|
||||
</Box>
|
||||
<MyTooltip label={t('common:core.chat.Restart')}>
|
||||
<IconButton
|
||||
className="chat"
|
||||
size={'smSquare'}
|
||||
icon={<MyIcon name={'common/clearLight'} w={'14px'} />}
|
||||
variant={'whiteDanger'}
|
||||
borderRadius={'md'}
|
||||
aria-label={'delete'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Box flex={1}>
|
||||
<HelperBot
|
||||
type={HelperBotTypeEnum.skillEditor}
|
||||
metadata={skillAgentMetadata}
|
||||
onApply={(e) => {
|
||||
console.log(e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</MyBox>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ChatTest);
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
import React, { useEffect, useMemo, useTransition } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Grid,
|
||||
type BoxProps,
|
||||
useTheme,
|
||||
useDisclosure,
|
||||
Button,
|
||||
HStack,
|
||||
Input,
|
||||
IconButton,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import type {
|
||||
AppFileSelectConfigType,
|
||||
AppFormEditFormType,
|
||||
SkillEditType
|
||||
} from '@fastgpt/global/core/app/type.d';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import VariableEdit from '@/components/core/app/VariableEdit';
|
||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
||||
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
|
||||
import SearchParamsTip from '@/components/core/dataset/SearchParamsTip';
|
||||
import SettingLLMModel from '@/components/core/ai/SettingLLMModel';
|
||||
import { TTSTypeEnum } from '@/web/core/app/constants';
|
||||
import { workflowSystemVariables } from '@/web/core/app/utils';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '@/pageComponents/app/detail/context';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import VariableTip from '@/components/common/Textarea/MyTextarea/VariableTip';
|
||||
import { getWebLLMModel } from '@/web/common/system/utils';
|
||||
import ToolSelect from '../../FormComponent/ToolSelector/ToolSelect';
|
||||
import OptimizerPopover from '@/components/common/PromptEditor/OptimizerPopover';
|
||||
import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { type SelectedToolItemType, useSkillManager } from '../hooks/useSkillManager';
|
||||
import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance';
|
||||
import { cardStyles } from '../../../constants';
|
||||
import { defaultSkill as defaultEditSkill } from './Row';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
|
||||
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
|
||||
const TTSSelect = dynamic(() => import('@/components/core/app/TTSSelect'));
|
||||
const QGConfig = dynamic(() => import('@/components/core/app/QGConfig'));
|
||||
const WhisperConfig = dynamic(() => import('@/components/core/app/WhisperConfig'));
|
||||
const InputGuideConfig = dynamic(() => import('@/components/core/app/InputGuideConfig'));
|
||||
const WelcomeTextConfig = dynamic(() => import('@/components/core/app/WelcomeTextConfig'));
|
||||
const FileSelectConfig = dynamic(() => import('@/components/core/app/FileSelect'));
|
||||
|
||||
const EditForm = ({
|
||||
model,
|
||||
fileSelectConfig,
|
||||
defaultSkill = defaultEditSkill,
|
||||
onClose,
|
||||
setAppForm
|
||||
}: {
|
||||
model: string;
|
||||
fileSelectConfig?: AppFileSelectConfigType;
|
||||
defaultSkill?: SkillEditType;
|
||||
onClose: () => void;
|
||||
setAppForm: React.Dispatch<React.SetStateAction<AppFormEditFormType>>;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const [, startTst] = useTransition();
|
||||
|
||||
const selectedModel = getWebLLMModel(model);
|
||||
|
||||
const { register, setValue, handleSubmit, reset, watch } = useForm<SkillEditType>({
|
||||
defaultValues: defaultSkill
|
||||
});
|
||||
useEffect(() => {
|
||||
reset(defaultSkill);
|
||||
}, [defaultSkill, reset]);
|
||||
|
||||
const name = watch('name');
|
||||
const prompt = watch('prompt');
|
||||
const selectedTools = watch('selectedTools');
|
||||
const selectDatasets = watch('dataset.list');
|
||||
|
||||
const {
|
||||
isOpen: isOpenDatasetSelect,
|
||||
onOpen: onOpenKbSelect,
|
||||
onClose: onCloseKbSelect
|
||||
} = useDisclosure();
|
||||
|
||||
const onSave = (e: SkillEditType) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
skills: e.id
|
||||
? state.skills.map((item) => (item.id === e.id ? e : item))
|
||||
: [{ ...e, id: getNanoid(6) }, ...state.skills]
|
||||
}));
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box p={5}>
|
||||
{/* Header */}
|
||||
<HStack gap={4}>
|
||||
<IconButton
|
||||
variant={'whiteBase'}
|
||||
icon={<MyIcon name="common/backLight" w={'1rem'} />}
|
||||
size={'smSquare'}
|
||||
aria-label={''}
|
||||
w={'32px'}
|
||||
h={'32px'}
|
||||
onClick={onClose}
|
||||
/>
|
||||
<Box color={'myGray.900'} flex={'1 0 0'} w={'0'} className={'textEllipsis'}>
|
||||
{name || t('app:skill_empty_name')}
|
||||
</Box>
|
||||
|
||||
<Button variant={'primary'} onClick={handleSubmit(onSave)}>
|
||||
{t('common:Save')}
|
||||
</Button>
|
||||
</HStack>
|
||||
{/* Name */}
|
||||
<HStack mt={5}>
|
||||
<FormLabel mr={3} required>
|
||||
{t('common:Name')}
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...register('name', { required: true })}
|
||||
maxLength={30}
|
||||
placeholder={t('app:skill_name_placeholder')}
|
||||
/>
|
||||
</HStack>
|
||||
{/* Desc */}
|
||||
<Box mt={4}>
|
||||
<HStack>
|
||||
<FormLabel mr={1} required>
|
||||
{t('common:descripton')}
|
||||
</FormLabel>
|
||||
<QuestionTip label={t('app:skill_description_placeholder')} />
|
||||
</HStack>
|
||||
<Textarea
|
||||
rows={3}
|
||||
mt={1}
|
||||
resize={'vertical'}
|
||||
{...register('description', { required: true })}
|
||||
placeholder={t('app:skill_description_placeholder')}
|
||||
/>
|
||||
</Box>
|
||||
{/* Prompt */}
|
||||
<Box mt={4}>
|
||||
<HStack w={'100%'}>
|
||||
<FormLabel>Prompt</FormLabel>
|
||||
</HStack>
|
||||
<Box mt={1}>
|
||||
<PromptEditor
|
||||
minH={100}
|
||||
maxH={300}
|
||||
value={prompt}
|
||||
onChange={(text) => {
|
||||
startTst(() => {
|
||||
setValue('prompt', text);
|
||||
});
|
||||
}}
|
||||
isRichText={false}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Tool select */}
|
||||
<Box mt={5} px={3} py={4} borderTop={'base'}>
|
||||
<ToolSelect
|
||||
selectedModel={selectedModel}
|
||||
selectedTools={selectedTools}
|
||||
fileSelectConfig={fileSelectConfig}
|
||||
onAddTool={(e) => {
|
||||
setValue('selectedTools', [e, ...(selectedTools || [])]);
|
||||
}}
|
||||
onUpdateTool={(e) => {
|
||||
setValue(
|
||||
'selectedTools',
|
||||
selectedTools?.map((item) => (item.id === e.id ? e : item)) || []
|
||||
);
|
||||
}}
|
||||
onRemoveTool={(id) => {
|
||||
setValue('selectedTools', selectedTools?.filter((item) => item.id !== id) || []);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* Dataset select */}
|
||||
<Box py={4} px={3} borderTop={'base'} borderBottom={'base'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'} flex={1}>
|
||||
<MyIcon name={'core/app/simpleMode/dataset'} w={'20px'} />
|
||||
<FormLabel ml={2}>{t('app:dataset')}</FormLabel>
|
||||
</Flex>
|
||||
<Button
|
||||
mr={'-5px'}
|
||||
variant={'transparentBase'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
fontSize={'sm'}
|
||||
onClick={onOpenKbSelect}
|
||||
>
|
||||
{t('common:Choose')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={[2, 4]}>
|
||||
{selectDatasets.map((item) => (
|
||||
<MyTooltip key={item.datasetId} label={t('common:core.dataset.Read Dataset')}>
|
||||
<Flex
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
bg={'white'}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={theme.borders.base}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
router.push({
|
||||
pathname: '/dataset/detail',
|
||||
query: {
|
||||
datasetId: item.datasetId
|
||||
}
|
||||
})
|
||||
}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'1.5rem'} borderRadius={'sm'} />
|
||||
<Box
|
||||
ml={2}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className={'textEllipsis'}
|
||||
fontSize={'sm'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{isOpenDatasetSelect && (
|
||||
<DatasetSelectModal
|
||||
defaultSelectedDatasets={selectDatasets.map((item) => ({
|
||||
datasetId: item.datasetId,
|
||||
vectorModel: item.vectorModel,
|
||||
name: item.name,
|
||||
avatar: item.avatar
|
||||
}))}
|
||||
onClose={onCloseKbSelect}
|
||||
onChange={(e) => {
|
||||
setValue('dataset.list', e);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(EditForm);
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import React from 'react';
|
||||
import { Box, Button, Flex, Grid, HStack, useDisclosure } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type { SkillEditType } from '@fastgpt/global/core/app/type';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
export const defaultSkill: SkillEditType = {
|
||||
id: '',
|
||||
name: '',
|
||||
description: '',
|
||||
prompt: '',
|
||||
dataset: {
|
||||
list: []
|
||||
},
|
||||
selectedTools: [],
|
||||
fileSelectConfig: {
|
||||
canSelectFile: false,
|
||||
canSelectImg: false,
|
||||
canSelectVideo: false,
|
||||
canSelectAudio: false,
|
||||
canSelectCustomFileExtension: false,
|
||||
customFileExtensionList: []
|
||||
}
|
||||
};
|
||||
|
||||
const Row = ({
|
||||
skills,
|
||||
onEditSkill,
|
||||
onDeleteSkill
|
||||
}: {
|
||||
skills: SkillEditType[];
|
||||
onEditSkill: (e: SkillEditType) => void;
|
||||
onDeleteSkill: (id: string) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'} flex={1}>
|
||||
<MyIcon name={'core/app/toolCall'} w={'20px'} />
|
||||
<FormLabel ml={2}>{t('app:skills')}</FormLabel>
|
||||
<QuestionTip ml={1} label={t('app:skills_tip')} />
|
||||
</Flex>
|
||||
<Button
|
||||
variant={'transparentBase'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
mr={'-5px'}
|
||||
size={'sm'}
|
||||
fontSize={'sm'}
|
||||
onClick={() => onEditSkill({ ...defaultSkill })}
|
||||
>
|
||||
{t('common:Add')}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
<Box mt={3}>
|
||||
{skills.map((skill) => (
|
||||
<HStack
|
||||
key={skill.id}
|
||||
justifyContent={'space-between'}
|
||||
py={2}
|
||||
px={4}
|
||||
borderRadius={'md'}
|
||||
border={'base'}
|
||||
_notLast={{
|
||||
mb: 2
|
||||
}}
|
||||
_hover={{
|
||||
bg: 'myGray.25'
|
||||
}}
|
||||
>
|
||||
<Box flex={'1 0 0'}>{skill.name}</Box>
|
||||
<MyIconButton icon={'edit'} onClick={() => onEditSkill(skill)} />
|
||||
<MyIconButton icon={'delete'} onClick={() => onDeleteSkill(skill.id)} />
|
||||
</HStack>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Row;
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
import type { AppChatConfigType, AppFormEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import type {
|
||||
AppChatConfigType,
|
||||
AppFormEditFormType,
|
||||
SkillEditType
|
||||
} from '@fastgpt/global/core/app/type';
|
||||
import type {
|
||||
FlowNodeTemplateType,
|
||||
StoreNodeItemType
|
||||
|
|
@ -51,11 +55,14 @@ export const appWorkflow2AgentForm = ({
|
|||
defaultAppForm.aiSettings.aiChatTopP = inputMap.get(NodeInputKeyEnum.aiChatTopP);
|
||||
|
||||
const subApps = inputMap.get(NodeInputKeyEnum.subApps) as FlowNodeTemplateType[];
|
||||
|
||||
if (subApps) {
|
||||
subApps.forEach((subApp) => {
|
||||
defaultAppForm.selectedTools.push(subApp);
|
||||
});
|
||||
defaultAppForm.selectedTools = subApps;
|
||||
}
|
||||
|
||||
// TODO: 临时存这里,后续会改成单独表存储
|
||||
const skills = inputMap.get(NodeInputKeyEnum.skills) as SkillEditType[];
|
||||
if (skills) {
|
||||
defaultAppForm.skills = skills;
|
||||
}
|
||||
} else if (node.flowNodeType === FlowNodeTypeEnum.systemConfig) {
|
||||
defaultAppForm.chatConfig = getAppChatConfig({
|
||||
|
|
@ -187,7 +194,7 @@ export function agentForm2AppWorkflow(
|
|||
key: NodeInputKeyEnum.subApps,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden], // Set in the pop-up window
|
||||
label: '',
|
||||
valueType: WorkflowIOValueTypeEnum.object,
|
||||
valueType: WorkflowIOValueTypeEnum.arrayObject,
|
||||
value: data.selectedTools.map((tool) => ({
|
||||
...tool,
|
||||
inputs: tool.inputs.map((input) => {
|
||||
|
|
@ -211,6 +218,38 @@ export function agentForm2AppWorkflow(
|
|||
return input;
|
||||
})
|
||||
}))
|
||||
},
|
||||
{
|
||||
key: NodeInputKeyEnum.skills,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden], // Set in the pop-up window
|
||||
label: '',
|
||||
valueType: WorkflowIOValueTypeEnum.arrayObject,
|
||||
value: data.skills.map((skill) => ({
|
||||
...skill,
|
||||
selectedTools: skill.selectedTools.map((tool) => ({
|
||||
...tool,
|
||||
inputs: tool.inputs.map((input) => {
|
||||
// Special key value
|
||||
if (input.key === NodeInputKeyEnum.forbidStream) {
|
||||
input.value = true;
|
||||
}
|
||||
// Special tool
|
||||
if (
|
||||
tool.flowNodeType === FlowNodeTypeEnum.appModule &&
|
||||
input.key === NodeInputKeyEnum.history
|
||||
) {
|
||||
return {
|
||||
...input,
|
||||
value: data.aiSettings.maxHistories
|
||||
};
|
||||
}
|
||||
if (input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)) {
|
||||
input.value = [[workflowStartNodeId, NodeOutputKeyEnum.userFiles]];
|
||||
}
|
||||
return input;
|
||||
})
|
||||
}))
|
||||
}))
|
||||
}
|
||||
],
|
||||
outputs: AgentNode.outputs
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
|||
import { useTranslation } from 'next-i18next';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { type AppFormEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import type {
|
||||
SelectedToolItemType,
|
||||
AppFormEditFormType,
|
||||
AppFileSelectConfigType
|
||||
} from '@fastgpt/global/core/app/type';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { theme } from '@fastgpt/web/styles/theme';
|
||||
import DeleteIcon, { hoverDeleteStyles } from '@fastgpt/web/components/common/Icon/delete';
|
||||
import ToolSelectModal, { childAppSystemKey } from './ToolSelectModal';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { hoverDeleteStyles } from '@fastgpt/web/components/common/Icon/delete';
|
||||
import ToolSelectModal from './ToolSelectModal';
|
||||
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import ConfigToolModal from '../../component/ConfigToolModal';
|
||||
import { getWebLLMModel } from '@/web/common/system/utils';
|
||||
|
|
@ -22,13 +22,22 @@ import { PluginStatusEnum, PluginStatusMap } from '@fastgpt/global/core/plugin/t
|
|||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import { checkNeedsUserConfiguration } from '../../ChatAgent/utils';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
|
||||
const ToolSelect = ({
|
||||
appForm,
|
||||
setAppForm
|
||||
selectedModel,
|
||||
selectedTools = [],
|
||||
fileSelectConfig = {},
|
||||
onAddTool,
|
||||
onUpdateTool,
|
||||
onRemoveTool
|
||||
}: {
|
||||
appForm: AppFormEditFormType;
|
||||
setAppForm: React.Dispatch<React.SetStateAction<AppFormEditFormType>>;
|
||||
selectedModel: LLMModelItemType;
|
||||
selectedTools?: SelectedToolItemType[];
|
||||
fileSelectConfig?: AppFileSelectConfigType;
|
||||
onAddTool: (tool: SelectedToolItemType) => void;
|
||||
onUpdateTool: (tool: SelectedToolItemType) => void;
|
||||
onRemoveTool: (id: string) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
@ -41,7 +50,6 @@ const ToolSelect = ({
|
|||
onOpen: onOpenToolsSelect,
|
||||
onClose: onCloseToolsSelect
|
||||
} = useDisclosure();
|
||||
const selectedModel = getWebLLMModel(appForm.aiSettings.model);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -64,11 +72,11 @@ const ToolSelect = ({
|
|||
</Button>
|
||||
</Flex>
|
||||
<Grid
|
||||
mt={appForm.selectedTools.length > 0 ? 2 : 0}
|
||||
mt={selectedTools.length > 0 ? 2 : 0}
|
||||
gridTemplateColumns={'repeat(2, minmax(0, 1fr))'}
|
||||
gridGap={[2, 4]}
|
||||
>
|
||||
{appForm.selectedTools.map((item) => {
|
||||
{selectedTools.map((item) => {
|
||||
const toolError = formatToolError(item.pluginData?.error);
|
||||
// 即将下架/已下架
|
||||
const status = item.status || item.pluginData?.status;
|
||||
|
|
@ -161,10 +169,7 @@ const ToolSelect = ({
|
|||
hoverColor="red.600"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setAppForm((state: AppFormEditFormType) => ({
|
||||
...state,
|
||||
selectedTools: state.selectedTools.filter((tool) => tool.id !== item.id)
|
||||
}));
|
||||
onRemoveTool(item.id);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
|
|
@ -175,20 +180,14 @@ const ToolSelect = ({
|
|||
|
||||
{isOpenToolsSelect && (
|
||||
<ToolSelectModal
|
||||
selectedTools={appForm.selectedTools}
|
||||
chatConfig={appForm.chatConfig}
|
||||
selectedTools={selectedTools}
|
||||
fileSelectConfig={fileSelectConfig}
|
||||
selectedModel={selectedModel}
|
||||
onAddTool={(e) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: [...state.selectedTools, e]
|
||||
}));
|
||||
onAddTool(e);
|
||||
}}
|
||||
onRemoveTool={(e) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: state.selectedTools.filter((item) => item.pluginId !== e.id)
|
||||
}));
|
||||
onRemoveTool(e.id);
|
||||
}}
|
||||
onClose={onCloseToolsSelect}
|
||||
/>
|
||||
|
|
@ -198,17 +197,10 @@ const ToolSelect = ({
|
|||
configTool={configTool}
|
||||
onCloseConfigTool={() => setConfigTool(null)}
|
||||
onAddTool={(e) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: state.selectedTools.map((item) =>
|
||||
item.pluginId === configTool.pluginId
|
||||
? {
|
||||
...e,
|
||||
configStatus: 'active'
|
||||
}
|
||||
: item
|
||||
)
|
||||
}));
|
||||
onUpdateTool({
|
||||
...e,
|
||||
configStatus: 'active'
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import { checkNeedsUserConfiguration, validateToolConfiguration } from '../../Ch
|
|||
|
||||
type Props = {
|
||||
selectedTools: FlowNodeTemplateType[];
|
||||
chatConfig: AppFormEditFormType['chatConfig'];
|
||||
fileSelectConfig: AppFormEditFormType['chatConfig']['fileSelectConfig'];
|
||||
selectedModel: LLMModelItemType;
|
||||
onAddTool: (tool: SelectedToolItemType) => void;
|
||||
onRemoveTool: (tool: NodeTemplateListItemType) => void;
|
||||
|
|
@ -244,7 +244,7 @@ const RenderList = React.memo(function RenderList({
|
|||
onRemoveTool,
|
||||
setParentId,
|
||||
selectedTools,
|
||||
chatConfig
|
||||
fileSelectConfig
|
||||
}: Props & {
|
||||
templates: NodeTemplateListItemType[];
|
||||
type: TemplateTypeEnum;
|
||||
|
|
@ -262,8 +262,8 @@ const RenderList = React.memo(function RenderList({
|
|||
|
||||
const toolValid = validateToolConfiguration({
|
||||
toolTemplate: res,
|
||||
canSelectFile: chatConfig?.fileSelectConfig?.canSelectFile,
|
||||
canSelectImg: chatConfig?.fileSelectConfig?.canSelectImg
|
||||
canSelectFile: fileSelectConfig?.canSelectFile,
|
||||
canSelectImg: fileSelectConfig?.canSelectImg
|
||||
});
|
||||
if (!toolValid) {
|
||||
return toast({
|
||||
|
|
|
|||
|
|
@ -327,7 +327,30 @@ const EditForm = ({
|
|||
|
||||
{/* tool choice */}
|
||||
<Box {...BoxStyles}>
|
||||
<ToolSelect appForm={appForm} setAppForm={setAppForm} />
|
||||
<ToolSelect
|
||||
selectedModel={selectedModel}
|
||||
selectedTools={appForm.selectedTools}
|
||||
fileSelectConfig={appForm.chatConfig.fileSelectConfig}
|
||||
onAddTool={(e) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: [e, ...(state.selectedTools || [])]
|
||||
}));
|
||||
}}
|
||||
onUpdateTool={(e) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools:
|
||||
state.selectedTools?.map((item) => (item.id === e.id ? e : item)) || []
|
||||
}));
|
||||
}}
|
||||
onRemoveTool={(id) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: state.selectedTools?.filter((item) => item.id !== id) || []
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* File select */}
|
||||
|
|
|
|||
|
|
@ -40,10 +40,13 @@ async function handler(req: ApiRequestProps<completionsBody>, res: ApiResponseTy
|
|||
|
||||
// 执行不同逻辑
|
||||
const fn = dispatchMap[metadata.type];
|
||||
if (!fn) {
|
||||
return Promise.reject('Invalid helper bot type');
|
||||
}
|
||||
const result = await fn({
|
||||
query,
|
||||
files,
|
||||
metadata,
|
||||
data: metadata.data,
|
||||
histories,
|
||||
workflowResponseWrite,
|
||||
user: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue