Skip to content

Commit

Permalink
feat: plugin run (#1950)
Browse files Browse the repository at this point in the history
* feat: plugin run

* fix

* ui

* fix

* change user input type

* fix

* fix

* temp

* split out plugin chat
  • Loading branch information
newfish-cmyk authored and c121914yu committed Jul 11, 2024
1 parent dd2a9bd commit 6b23ee6
Show file tree
Hide file tree
Showing 29 changed files with 1,267 additions and 446 deletions.
9 changes: 5 additions & 4 deletions packages/global/core/chat/adapt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,17 @@ export const chats2GPTMessages = ({
text: item.text?.content || ''
};
}
if (item.type === 'file' && item.file?.type === ChatFileTypeEnum.image) {
if (
item.type === ChatItemValueTypeEnum.file &&
item.file?.type === ChatFileTypeEnum.image
) {
return {
type: 'image_url',
image_url: {
url: item.file?.url || ''
}
};
}
return;
})
.filter(Boolean) as ChatCompletionContentPart[];

Expand Down Expand Up @@ -166,7 +168,7 @@ export const GPTMessages2Chats = (
} else if (item.type === 'image_url') {
value.push({
//@ts-ignore
type: 'file',
type: ChatItemValueTypeEnum.file,
file: {
type: ChatFileTypeEnum.image,
name: '',
Expand All @@ -175,7 +177,6 @@ export const GPTMessages2Chats = (
});
}
});
// @ts-ignore
}
} else if (
obj === ChatRoleEnum.AI &&
Expand Down
1 change: 1 addition & 0 deletions packages/global/core/chat/type.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export type ChatSiteItemType = (UserChatItemType | SystemChatItemType | AIChatIt
status: `${ChatStatusEnum}`;
moduleName?: string;
ttsBuffer?: Uint8Array;
responseData?: ChatHistoryItemResType[];
} & ChatBoxInputType;

/* --------- team chat --------- */
Expand Down
36 changes: 36 additions & 0 deletions packages/global/core/workflow/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
defaultWhisperConfig
} from '../app/constants';
import { IfElseResultEnum } from './template/system/ifElse/constant';
import { RuntimeNodeItemType } from './runtime/type';

export const getHandleId = (nodeId: string, type: 'source' | 'target', key: string) => {
return `${nodeId}-${type}-${key}`;
Expand Down Expand Up @@ -190,3 +191,38 @@ export const isReferenceValue = (value: any): boolean => {
export const getElseIFLabel = (i: number) => {
return i === 0 ? IfElseResultEnum.IF : `${IfElseResultEnum.ELSE_IF} ${i}`;
};

// add value to plugin input node when run plugin
export const updatePluginInputNodeInputs = (
nodes: RuntimeNodeItemType[],
variables: Record<string, any>
) => {
return nodes.map((node) =>
node.flowNodeType === FlowNodeTypeEnum.pluginInput
? {
...node,
inputs: node.inputs.map((input) => {
let parseValue = (() => {
try {
if (
input.valueType === WorkflowIOValueTypeEnum.string ||
input.valueType === WorkflowIOValueTypeEnum.number ||
input.valueType === WorkflowIOValueTypeEnum.boolean
)
return variables[input.key];

return JSON.parse(variables[input.key]);
} catch (e) {
return variables[input.key];
}
})();

return {
...input,
value: parseValue ?? input.value
};
})
}
: node
);
};
1 change: 1 addition & 0 deletions packages/web/i18n/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
"Rename Success": "Rename Success",
"Request Error": "Request Error",
"Require Input": "Required Input",
"Restart": "Restart",
"Role": "Role",
"Root folder": "Root folder",
"Save": "Save",
Expand Down
1 change: 1 addition & 0 deletions packages/web/i18n/zh/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"Rename Success": "重命名成功",
"Request Error": "请求异常",
"Require Input": "必填",
"Restart": "重新开始",
"Role": "权限",
"Root folder": "根目录",
"Save": "保存",
Expand Down
239 changes: 239 additions & 0 deletions projects/app/src/components/ChatBox/PluginChatBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import { useCallback, useEffect, useMemo, useRef } from 'react';
import PluginBox from './components/PluginBox';
import { ChatTypeEnum } from './constants';
import { Box } from '@chakra-ui/react';
import { useForm, UseFormHandleSubmit } from 'react-hook-form';
import { useContextSelector } from 'use-context-selector';
import { ChatBoxContext } from './Provider';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import {
ChatBoxInputFormType,
ChatBoxInputType,
generatingMessageProps,
StartChatFnProps
} from './type';
import { ChatSiteItemType, UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { StreamResponseType } from '@/web/common/api/fetch';
import { useTranslation } from 'react-i18next';

const PluginChatBox = ({
chatType,
pluginInputs,
onStartChat,
handleSubmit,
generatingMessage
}: {
chatType: `${ChatTypeEnum}`;
pluginInputs: FlowNodeInputItemType[];
onStartChat?: (e: StartChatFnProps) => Promise<
StreamResponseType & {
isNewChat?: boolean;
}
>;
handleSubmit: UseFormHandleSubmit<ChatBoxInputFormType>;
generatingMessage: ({
event,
text,
status,
name,
tool,
autoTTSResponse,
variables
}: generatingMessageProps & {
autoTTSResponse?: boolean;
}) => void;
}) => {
const {
variableList,
startSegmentedAudio,
finishSegmentedAudio,
setAudioPlayingChatId,
splitText2Audio,
chatHistories,
setChatHistories,
isChatting
} = useContextSelector(ChatBoxContext, (v) => v);
const pluginForm = useForm();
const { handleSubmit: handlePluginSubmit, control: pluginControl, reset } = pluginForm;

const ChatBoxRef = useRef<HTMLDivElement>(null);
const chatController = useRef(new AbortController());
const { toast } = useToast();
const { t } = useTranslation();

const currentPluginInputs = useMemo(() => {
return chatHistories.length > 0
? JSON.parse(chatHistories[0]?.value[0].text?.content || '{}').format
: pluginInputs;
}, [chatHistories, pluginInputs]);

useEffect(() => {
function convertArrayToJson(arr: FlowNodeInputItemType[]) {
let result: Record<string, any> = {};
arr?.forEach((item) => {
result[item.key] = item.defaultValue || '';
});
return result;
}

const pluginVariables = convertArrayToJson(currentPluginInputs);
reset({
...pluginVariables,
...JSON.parse(chatHistories[0]?.value[0].text?.content || '{}').value
});
}, [chatHistories]);

const sendPrompt = useCallback(
({
text = '',
pluginVariables
}: ChatBoxInputType & {
autoTTSResponse?: boolean;
pluginVariables?: any;
}) => {
handleSubmit(
async ({ variables }) => {
if (!onStartChat) return;
if (isChatting) {
toast({
title: '正在聊天中...请等待结束',
status: 'warning'
});
return;
}

text = text.trim();

// delete invalid variables, 只保留在 variableList 中的变量
const requestVariables: Record<string, any> = {};
variableList?.forEach((item) => {
requestVariables[item.key] = variables[item.key] || '';
});

const responseChatId = getNanoid(24);

const newChatList: ChatSiteItemType[] = [
{
dataId: getNanoid(24),
obj: ChatRoleEnum.Human,
value: [
{
type: ChatItemValueTypeEnum.text,
text: {
content: JSON.stringify({
value: pluginVariables,
format: pluginInputs
})
}
}
] as UserChatItemValueItemType[],
status: 'finish'
},
{
dataId: responseChatId,
obj: ChatRoleEnum.AI,
value: [
{
type: ChatItemValueTypeEnum.text,
text: {
content: ''
}
}
],
status: 'loading'
}
];

// 插入内容
setChatHistories(newChatList);

try {
// create abort obj
const abortSignal = new AbortController();
chatController.current = abortSignal;

const messages = chats2GPTMessages({ messages: newChatList, reserveId: true });

const { responseData } = await onStartChat({
chatList: newChatList,
messages,
controller: abortSignal,
generatingMessage: (e) => generatingMessage({ ...e }),
variables: pluginVariables
});

// set finish status
setChatHistories((state) =>
state.map((item, index) => {
if (index !== state.length - 1) return item;
return {
...item,
status: 'finish',
responseData
};
})
);
} catch (err: any) {
toast({
title: t(getErrText(err, 'core.chat.error.Chat error')),
status: 'error',
duration: 5000,
isClosable: true
});

if (!err?.responseText) {
setChatHistories(newChatList.slice(0, newChatList.length - 2));
}

// set finish status
setChatHistories((state) =>
state.map((item, index) => {
if (index !== state.length - 1) return item;
return {
...item,
status: 'finish'
};
})
);
}
},
(err) => {
console.log(err?.variables);
}
)();
},
[
chatHistories,
finishSegmentedAudio,
isChatting,
pluginInputs,
setAudioPlayingChatId,
setChatHistories,
splitText2Audio,
startSegmentedAudio,
variableList
]
);

return (
<Box ref={ChatBoxRef} flex={'1 0 0'} h={0} w={'100%'} overflow={'overlay'} px={[4, 0]} pb={3}>
<PluginBox
chatType={chatType}
pluginInputs={currentPluginInputs}
control={pluginControl}
handleSubmit={handlePluginSubmit}
sendPrompt={sendPrompt}
chatHistories={chatHistories}
isChatting={isChatting}
onStartChat={onStartChat}
/>
</Box>
);
};

export default PluginChatBox;
Loading

0 comments on commit 6b23ee6

Please sign in to comment.