Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: plugin run #1950

Merged
merged 9 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading