Concat plugin to app (#1799)

This commit is contained in:
Archer
2024-06-19 14:38:21 +08:00
committed by GitHub
parent b17d14bb7d
commit 565bfc8486
220 changed files with 5018 additions and 4667 deletions

View File

@@ -1,3 +1,5 @@
'use client';
import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
@@ -21,9 +23,10 @@ export const useAudioPlay = (props?: OutLinkChatAuthProps & { ttsConfig?: AppTTS
// Check whether the voice is supported
const hasAudio = (() => {
if (typeof window === 'undefined') return false;
if (ttsConfig?.type === TTSTypeEnum.none) return false;
if (ttsConfig?.type === TTSTypeEnum.model) return true;
const voices = window.speechSynthesis?.getVoices?.() || []; // 获取语言包
const voices = window?.speechSynthesis?.getVoices?.() || []; // 获取语言包
const voice = voices.find((item) => {
return item.lang === 'zh-CN' || item.lang === 'zh';
});
@@ -69,9 +72,9 @@ export const useAudioPlay = (props?: OutLinkChatAuthProps & { ttsConfig?: AppTTS
);
const playWebAudio = useCallback((text: string) => {
// window speech
window.speechSynthesis?.cancel();
window?.speechSynthesis?.cancel();
const msg = new SpeechSynthesisUtterance(text);
const voices = window.speechSynthesis?.getVoices?.() || []; // 获取语言包
const voices = window?.speechSynthesis?.getVoices?.() || []; // 获取语言包
const voice = voices.find((item) => {
return item.lang === 'zh-CN';
});

View File

@@ -2,6 +2,10 @@ import { DELETE, GET, POST } from '@/web/common/api/request';
import type { CreateAppFolderBody } from '@/pages/api/core/app/folder/create';
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import type {
transitionWorkflowBody,
transitionWorkflowResponse
} from '@/pages/api/core/app/transitionWorkflow';
/* folder */
export const postCreateAppFolder = (data: CreateAppFolderBody) =>
@@ -9,3 +13,8 @@ export const postCreateAppFolder = (data: CreateAppFolderBody) =>
export const getAppFolderPath = (parentId: ParentIdType) =>
GET<ParentTreePathItemType[]>(`/core/app/folder/path`, { parentId });
/* detail */
export const postTransition2Workflow = (data: transitionWorkflowBody) =>
POST<transitionWorkflowResponse>('/core/app/transitionWorkflow', data);

View File

@@ -0,0 +1,50 @@
import { DELETE, GET, POST } from '@/web/common/api/request';
import type { createHttpPluginBody } from '@/pages/api/core/app/httpPlugin/create';
import type { UpdateHttpPluginBody } from '@/pages/api/core/app/httpPlugin/update';
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type';
import { getMyApps } from '../api';
import type { ListAppBody } from '@/pages/api/core/app/list';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
import type { GetPreviewNodeQuery } from '@/pages/api/core/app/plugin/getPreviewNode';
/* ============ team plugin ============== */
export const getTeamPlugTemplates = (data?: ListAppBody) =>
getMyApps(data).then((res) =>
res.map<FlowNodeTemplateType>((app) => ({
id: app._id,
pluginId: app._id,
pluginType: app.type,
templateType: FlowNodeTemplateTypeEnum.personalPlugin,
flowNodeType: FlowNodeTypeEnum.pluginModule,
avatar: app.avatar,
name: app.name,
intro: app.intro,
showStatus: false,
version: app.pluginData?.nodeVersion || '481',
inputs: [],
outputs: []
}))
);
export const getSystemPlugTemplates = () =>
GET<FlowNodeTemplateType[]>('/core/app/plugin/getSystemPluginTemplates');
export const getPreviewPluginNode = (data: GetPreviewNodeQuery) =>
GET<FlowNodeTemplateType>('/core/app/plugin/getPreviewNode', data);
/* ============ http plugin ============== */
export const postCreateHttpPlugin = (data: createHttpPluginBody) =>
POST('/core/app/httpPlugin/create', data);
export const putUpdateHttpPlugin = (body: UpdateHttpPluginBody) =>
POST('/core/app/httpPlugin/update', body);
export const getApiSchemaByUrl = (url: string) =>
POST<Object>(
'/core/app/httpPlugin/getApiSchemaByUrl',
{ url },
{
timeout: 30000
}
);

View File

@@ -2,6 +2,13 @@ import { PostPublishAppProps, PostRevertAppProps } from '@/global/core/app/api';
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import type {
getLatestVersionQuery,
getLatestVersionResponse
} from '@/pages/api/core/app/version/latest';
export const getAppLatestVersion = (data: getLatestVersionQuery) =>
GET<getLatestVersionResponse>('/core/app/version/latest', data);
export const postPublishApp = (appId: string, data: PostPublishAppProps) =>
POST(`/core/app/version/publish?appId=${appId}`, data);

View File

@@ -1,114 +0,0 @@
import { Dispatch, ReactNode, SetStateAction, useCallback, useState } from 'react';
import { createContext } from 'use-context-selector';
import { defaultApp } from '../constants';
import { getAppDetailById, putAppById } from '../api';
import { useRequest } from 'ahooks';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { AppDetailType } from '@fastgpt/global/core/app/type';
import { AppUpdateParams, PostPublishAppProps } from '@/global/core/app/api';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { postPublishApp } from '../versionApi';
type AppContextType = {
appDetail: AppDetailType;
setAppDetail: Dispatch<SetStateAction<AppDetailType>>;
loadingApp: boolean;
updateAppDetail: (data: AppUpdateParams) => Promise<void>;
publishApp: (data: PostPublishAppProps) => Promise<void>;
};
export const AppContext = createContext<AppContextType>({
appDetail: defaultApp,
setAppDetail: function (value: SetStateAction<AppDetailType>): void {
throw new Error('Function not implemented.');
},
loadingApp: false,
updateAppDetail: function (data: AppUpdateParams): Promise<void> {
throw new Error('Function not implemented.');
},
publishApp: function (data: PostPublishAppProps): Promise<void> {
throw new Error('Function not implemented.');
}
});
export const AppContextProvider = ({ children, appId }: { children: ReactNode; appId: string }) => {
const { t } = useTranslation();
const router = useRouter();
const { toast } = useToast();
const [appDetail, setAppDetail] = useState(defaultApp);
const { loading } = useRequest(
() => {
if (appId) {
return getAppDetailById(appId);
}
return Promise.resolve(defaultApp);
},
{
refreshDeps: [appId],
onSuccess(res) {
setAppDetail(res);
},
onError(err: any) {
toast({
title: err?.message || t('core.app.error.Get app failed'),
status: 'error'
});
router.replace('/app/list');
}
}
);
const updateAppDetail = useCallback(
async (data: AppUpdateParams) => {
try {
await putAppById(appId, data);
setAppDetail((state) => {
return {
...state,
...data,
modules: data?.nodes || state.modules
};
});
} catch (error) {
toast({
status: 'warning',
title: getErrText(error)
});
}
},
[appId, toast]
);
const publishApp = useCallback(
async (data: PostPublishAppProps) => {
try {
await postPublishApp(appId, data);
setAppDetail((state) => {
return {
...state,
...data,
modules: data?.nodes || state.modules
};
});
} catch (error) {
toast({
status: 'warning',
title: getErrText(error)
});
}
},
[appId, toast]
);
const contextValue = {
appDetail,
setAppDetail,
loadingApp: loading,
updateAppDetail,
publishApp
};
return <AppContext.Provider value={contextValue}>{children}</AppContext.Provider>;
};

View File

@@ -7,12 +7,14 @@ import {
FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
// template
export const appTemplates: (AppItemType & {
type TemplateType = (AppItemType & {
avatar: string;
intro: string;
type: AppTypeEnum;
})[] = [
})[];
// template
export const simpleBotTemplates: TemplateType = [
{
id: 'simpleChat',
avatar: '/imgs/workflow/AI.png',
@@ -817,13 +819,16 @@ export const appTemplates: (AppItemType & {
targetHandle: '7BdojPlukIQw-target-left'
}
]
},
}
];
export const workflowTemplates: TemplateType = [
{
id: 'CQ',
avatar: '/imgs/workflow/cq.png',
name: '问题分类 + 知识库',
intro: '先对用户的问题进行分类,再根据不同类型问题,执行不同的操作',
type: AppTypeEnum.advanced,
type: AppTypeEnum.workflow,
modules: [
{
nodeId: 'userGuide',
@@ -1273,3 +1278,46 @@ export const appTemplates: (AppItemType & {
]
}
];
export const pluginTemplates: TemplateType = [
{
id: 'plugin-simple',
avatar: '/imgs/workflow/AI.png',
name: '默认模板',
intro: '标准的插件初始模板',
type: AppTypeEnum.plugin,
modules: [
{
nodeId: 'pluginInput',
name: '自定义插件输入',
avatar: '/imgs/workflow/input.png',
flowNodeType: FlowNodeTypeEnum.pluginInput,
showStatus: false,
position: {
x: 616.4226348688949,
y: -165.05298493910115
},
version: '481',
inputs: [],
outputs: []
},
{
nodeId: 'pluginOutput',
name: '自定义插件输出',
avatar: '/imgs/workflow/output.png',
flowNodeType: FlowNodeTypeEnum.pluginOutput,
showStatus: false,
position: {
x: 1607.7142331269126,
y: -151.8669210746189
},
version: '481',
inputs: [],
outputs: []
}
],
edges: []
}
];
export const defaultAppTemplates = simpleBotTemplates.concat(workflowTemplates[0]);

View File

@@ -1,6 +1,6 @@
import {
AppChatConfigType,
AppDetailType,
ChatInputGuideConfigType,
AppSchema,
AppSimpleEditFormType
} from '@fastgpt/global/core/app/type';
@@ -16,12 +16,15 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
import { TFunction } from 'next-i18next';
import { ToolModule } from '@fastgpt/global/core/workflow/template/system/tools';
type WorkflowType = {
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
};
export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType {
export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType & {
chatConfig: AppChatConfigType;
} {
const workflowStartNodeId = 'workflowStartNodeId';
function systemConfigTemplate(formData: AppSimpleEditFormType): StoreNodeItemType {
return {
@@ -663,7 +666,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType {
value: [workflowStartNodeId, 'userChatInput']
}
],
outputs: []
outputs: ToolModule.outputs
},
// tool nodes
...(datasetTool ? datasetTool.nodes : []),
@@ -693,7 +696,8 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType {
return {
nodes: [systemConfigTemplate(data), workflowStartTemplate(), ...workflow.nodes],
edges: workflow.edges
edges: workflow.edges,
chatConfig: data.chatConfig
};
}

View File

@@ -1,41 +0,0 @@
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
import {
CreateOnePluginParams,
PluginListItemType,
UpdatePluginParams
} from '@fastgpt/global/core/plugin/controller';
import { PluginItemSchema } from '@fastgpt/global/core/plugin/type';
export const postCreatePlugin = (data: CreateOnePluginParams) =>
POST<string>('/core/plugin/create', data);
export const putUpdatePlugin = (data: UpdatePluginParams) => PUT('/core/plugin/update', data);
export const getPluginPaths = (parentId?: string) =>
GET<ParentTreePathItemType[]>('/core/plugin/paths', { parentId });
// http plugin
export const getApiSchemaByUrl = (url: string) =>
POST<Object>(
'/core/plugin/httpPlugin/getApiSchemaByUrl',
{ url },
{
timeout: 30000
}
);
/* work flow */
export const getPlugTemplates = () => GET<FlowNodeTemplateType[]>('/core/plugin/templates');
export const getUserPlugins = (data: { parentId?: string; type?: `${PluginTypeEnum}` }) =>
GET<PluginListItemType[]>('/core/plugin/list', data);
export const getTeamPlugTemplates = (data: { parentId?: string | null; searchKey?: string }) =>
GET<FlowNodeTemplateType[]>('/core/plugin/pluginTemplate/getTeamPluginTemplates', data);
export const getSystemPlugTemplates = () =>
GET<FlowNodeTemplateType[]>('/core/plugin/pluginTemplate/getSystemPluginTemplates');
export const getPreviewPluginModule = (id: string) =>
GET<FlowNodeTemplateType>('/core/plugin/getPreviewNode', { id });
export const getOnePlugin = (id: string) => GET<PluginItemSchema>('/core/plugin/detail', { id });
export const delOnePlugin = (pluginId: string) => DELETE('/core/plugin/delete', { pluginId });

View File

@@ -1,56 +0,0 @@
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
import { getTeamPlugTemplates, getSystemPlugTemplates } from '../../plugin/api';
type State = {
systemNodeTemplates: FlowNodeTemplateType[];
loadSystemNodeTemplates: (init?: boolean) => Promise<FlowNodeTemplateType[]>;
teamPluginNodeTemplates: FlowNodeTemplateType[];
loadTeamPluginNodeTemplates: (e?: {
parentId?: string | null;
searchKey?: string;
init?: boolean;
}) => Promise<FlowNodeTemplateType[]>;
};
export const useWorkflowStore = create<State>()(
devtools(
persist(
immer((set, get) => ({
systemNodeTemplates: [],
async loadSystemNodeTemplates(init) {
if (!init && get().systemNodeTemplates.length > 0) {
return get().systemNodeTemplates;
}
const templates = await getSystemPlugTemplates();
set((state) => {
state.systemNodeTemplates = templates;
});
return templates;
},
teamPluginNodeTemplates: [],
async loadTeamPluginNodeTemplates(e) {
const { parentId = null, searchKey, init } = e || {};
if (!init && get().teamPluginNodeTemplates.length > 0) {
return get().teamPluginNodeTemplates;
}
const templates = await getTeamPlugTemplates({
parentId: parentId || null,
searchKey: searchKey
});
set((state) => {
state.teamPluginNodeTemplates = templates;
});
return templates;
}
})),
{
name: 'datasetStore',
partialize: (state) => ({})
}
)
)
);

View File

@@ -32,6 +32,7 @@ import {
import { IfElseListItemType } from '@fastgpt/global/core/workflow/template/system/ifElse/type';
import { VariableConditionEnum } from '@fastgpt/global/core/workflow/template/system/ifElse/constant';
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
import { cloneDeep, isEqual } from 'lodash';
export const nodeTemplate2FlowNode = ({
template,
@@ -377,3 +378,84 @@ export const updateFlowNodeVersion = (
return updatedNode;
};
type WorkflowType = {
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
chatConfig: AppChatConfigType;
};
export const compareWorkflow = (workflow1: WorkflowType, workflow2: WorkflowType) => {
const clone1 = cloneDeep(workflow1);
const clone2 = cloneDeep(workflow2);
if (!isEqual(clone1.edges, clone2.edges)) {
console.log('Edge not equal');
return false;
}
if (
clone1.chatConfig &&
clone2.chatConfig &&
!isEqual(
{
welcomeText: clone1.chatConfig?.welcomeText || '',
variables: clone1.chatConfig?.variables || [],
questionGuide: clone1.chatConfig?.questionGuide || false,
ttsConfig: clone1.chatConfig?.ttsConfig || undefined,
whisperConfig: clone1.chatConfig?.whisperConfig || undefined,
scheduledTriggerConfig: clone1.chatConfig?.scheduledTriggerConfig || undefined,
chatInputGuide: clone1.chatConfig?.chatInputGuide || undefined
},
{
welcomeText: clone2.chatConfig?.welcomeText || '',
variables: clone2.chatConfig?.variables || [],
questionGuide: clone2.chatConfig?.questionGuide || false,
ttsConfig: clone2.chatConfig?.ttsConfig || undefined,
whisperConfig: clone2.chatConfig?.whisperConfig || undefined,
scheduledTriggerConfig: clone2.chatConfig?.scheduledTriggerConfig || undefined,
chatInputGuide: clone2.chatConfig?.chatInputGuide || undefined
}
)
) {
console.log('chatConfig not equal');
return false;
}
const node1 = clone1.nodes.filter(Boolean).map((node) => ({
flowNodeType: node.flowNodeType,
inputs: node.inputs.map((input) => ({
...input,
value: input.value ?? undefined
})),
outputs: node.outputs.map((input) => ({
...input,
value: input.value ?? undefined
})),
name: node.name,
intro: node.intro,
avatar: node.avatar,
version: node.version,
position: node.position
}));
const node2 = clone2.nodes.filter(Boolean).map((node) => ({
flowNodeType: node.flowNodeType,
inputs: node.inputs.map((input) => ({
...input,
value: input.value ?? undefined
})),
outputs: node.outputs.map((input) => ({
...input,
value: input.value ?? undefined
})),
name: node.name,
intro: node.intro,
avatar: node.avatar,
version: node.version,
position: node.position
}));
// console.log(node1);
// console.log(node2);
return isEqual(node1, node2);
};