mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-13 14:29:40 +00:00
feat: plan interactive type
This commit is contained in:
8
packages/global/core/chat/type.d.ts
vendored
8
packages/global/core/chat/type.d.ts
vendored
@@ -118,9 +118,9 @@ export type ChatItemValueItemType =
|
||||
| UserChatItemValueItemType
|
||||
| SystemChatItemValueItemType
|
||||
| AIChatItemValueItemType;
|
||||
export type ChatItemMergeType = UserChatItemType | SystemChatItemType | AIChatItemType;
|
||||
export type ChatItemObjItemType = UserChatItemType | SystemChatItemType | AIChatItemType;
|
||||
|
||||
export type ChatItemSchema = ChatItemMergeType & {
|
||||
export type ChatItemSchema = ChatItemObjItemType & {
|
||||
dataId: string;
|
||||
chatId: string;
|
||||
userId: string;
|
||||
@@ -145,12 +145,12 @@ export type ResponseTagItemType = {
|
||||
toolCiteLinks?: ToolCiteLinksType[];
|
||||
};
|
||||
|
||||
export type ChatItemType = ChatItemMergeType & {
|
||||
export type ChatItemType = ChatItemObjItemType & {
|
||||
dataId?: string;
|
||||
} & ResponseTagItemType;
|
||||
|
||||
// Frontend type
|
||||
export type ChatSiteItemType = ChatItemMergeType & {
|
||||
export type ChatSiteItemType = ChatItemObjItemType & {
|
||||
_id?: string;
|
||||
dataId: string;
|
||||
status: `${ChatStatusEnum}`;
|
||||
|
@@ -41,3 +41,6 @@ export const needReplaceReferenceInputTypeList = [
|
||||
FlowNodeInputTypeEnum.addInputParam,
|
||||
FlowNodeInputTypeEnum.custom
|
||||
] as string[];
|
||||
|
||||
// Interactive
|
||||
export const ConfirmPlanAgentText = 'CONFIRM';
|
||||
|
@@ -197,14 +197,32 @@ export const getLastInteractiveValue = (
|
||||
|
||||
// Check is user select
|
||||
if (
|
||||
lastValue.interactive.type === 'userSelect' &&
|
||||
!lastValue.interactive.params.userSelectedVal
|
||||
(lastValue.interactive.type === 'userSelect' ||
|
||||
lastValue.interactive.type === 'agentPlanAskUserSelect') &&
|
||||
!lastValue.interactive?.params?.userSelectedVal
|
||||
) {
|
||||
return lastValue.interactive;
|
||||
}
|
||||
|
||||
// Check is user input
|
||||
if (lastValue.interactive.type === 'userInput' && !lastValue.interactive.params.submitted) {
|
||||
if (
|
||||
(lastValue.interactive.type === 'userInput' ||
|
||||
lastValue.interactive.type === 'agentPlanAskUserForm') &&
|
||||
!lastValue.interactive?.params?.submitted
|
||||
) {
|
||||
return lastValue.interactive;
|
||||
}
|
||||
|
||||
// Agent plan check
|
||||
if (
|
||||
lastValue.interactive.type === 'agentPlanCheck' &&
|
||||
!lastValue.interactive?.params?.confirmed
|
||||
) {
|
||||
return lastValue.interactive;
|
||||
}
|
||||
|
||||
// Agent plan ask query
|
||||
if (lastValue.interactive.type === 'agentPlanAskQuery') {
|
||||
return lastValue.interactive;
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import type { NodeOutputItemType } from '../../../../chat/type';
|
||||
import type { FlowNodeOutputItemType } from '../../../type/io';
|
||||
import type { FlowNodeInputTypeEnum } from 'core/workflow/node/constant';
|
||||
import type { WorkflowIOValueTypeEnum } from 'core/workflow/constants';
|
||||
import type { ChatCompletionMessageParam } from '../../../../ai/type';
|
||||
import type { RuntimeEdgeItemType } from '../../../type/edge';
|
||||
|
||||
type InteractiveBasicType = {
|
||||
export type InteractiveBasicType = {
|
||||
entryNodeIds: string[];
|
||||
memoryEdges: RuntimeEdgeItemType[];
|
||||
nodeOutputs: NodeOutputItemType[];
|
||||
@@ -40,12 +40,26 @@ type LoopInteractive = InteractiveNodeType & {
|
||||
};
|
||||
};
|
||||
|
||||
// Agent Interactive
|
||||
export type AgentPlanCheckInteractive = InteractiveNodeType & {
|
||||
type: 'agentPlanCheck';
|
||||
params: {
|
||||
confirmed?: boolean;
|
||||
};
|
||||
};
|
||||
export type AgentPlanAskQueryInteractive = InteractiveNodeType & {
|
||||
type: 'agentPlanAskQuery';
|
||||
params: {
|
||||
content: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type UserSelectOptionItemType = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
type UserSelectInteractive = InteractiveNodeType & {
|
||||
type: 'userSelect';
|
||||
export type UserSelectInteractive = InteractiveNodeType & {
|
||||
type: 'userSelect' | 'agentPlanAskUserSelect';
|
||||
params: {
|
||||
description: string;
|
||||
userSelectOptions: UserSelectOptionItemType[];
|
||||
@@ -71,8 +85,8 @@ export type UserInputFormItemType = {
|
||||
// select
|
||||
list?: { label: string; value: string }[];
|
||||
};
|
||||
type UserInputInteractive = InteractiveNodeType & {
|
||||
type: 'userInput';
|
||||
export type UserInputInteractive = InteractiveNodeType & {
|
||||
type: 'userInput' | 'agentPlanAskUserForm';
|
||||
params: {
|
||||
description: string;
|
||||
inputForm: UserInputFormItemType[];
|
||||
@@ -84,6 +98,8 @@ export type InteractiveNodeResponseType =
|
||||
| UserSelectInteractive
|
||||
| UserInputInteractive
|
||||
| ChildrenInteractive
|
||||
| LoopInteractive;
|
||||
| LoopInteractive
|
||||
| AgentPlanCheckInteractive
|
||||
| AgentPlanAskQueryInteractive;
|
||||
|
||||
export type WorkflowInteractiveResponseType = InteractiveBasicType & InteractiveNodeResponseType;
|
@@ -314,10 +314,12 @@ export const updateInteractiveChat = async ({
|
||||
// Update interactive value
|
||||
const interactiveValue = chatItem.value[chatItem.value.length - 1];
|
||||
|
||||
if (!interactiveValue || !interactiveValue.interactive?.params) {
|
||||
if (!interactiveValue || !interactiveValue.interactive) {
|
||||
return;
|
||||
}
|
||||
interactiveValue.interactive.params = interactiveValue.interactive.params || {};
|
||||
|
||||
// Update interactive value
|
||||
const parsedUserInteractiveVal = (() => {
|
||||
const { text: userInteractiveVal } = chatValue2RuntimePrompt(userContent.value);
|
||||
try {
|
||||
@@ -337,7 +339,7 @@ export const updateInteractiveChat = async ({
|
||||
if (finalInteractive.type === 'userSelect') {
|
||||
finalInteractive.params.userSelectedVal = parsedUserInteractiveVal;
|
||||
} else if (
|
||||
finalInteractive.type === 'userInput' &&
|
||||
(finalInteractive.type === 'userInput' || finalInteractive.type === 'agentPlanAskUserForm') &&
|
||||
typeof parsedUserInteractiveVal === 'object'
|
||||
) {
|
||||
finalInteractive.params.inputForm = finalInteractive.params.inputForm.map((item) => {
|
||||
@@ -350,6 +352,8 @@ export const updateInteractiveChat = async ({
|
||||
: item;
|
||||
});
|
||||
finalInteractive.params.submitted = true;
|
||||
} else if (finalInteractive.type === 'agentPlanCheck') {
|
||||
finalInteractive.params.confirmed = true;
|
||||
}
|
||||
|
||||
if (aiResponse.customFeedbacks) {
|
||||
|
@@ -1,4 +1,8 @@
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
NodeInputKeyEnum,
|
||||
NodeOutputKeyEnum,
|
||||
WorkflowIOValueTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
DispatchNodeResponseKeyEnum,
|
||||
SseResponseEventEnum
|
||||
@@ -41,7 +45,10 @@ import { dispatchPlanAgent } from './sub/plan';
|
||||
import { dispatchModelAgent } from './sub/model';
|
||||
import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { getSubApps, rewriteSubAppsToolset } from './sub';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { dispatchTool } from './sub/tool';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { getMasterAgentDefaultPrompt } from './constants';
|
||||
@@ -98,34 +105,33 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
||||
planAgentConfig
|
||||
}
|
||||
} = props;
|
||||
const memoryKey = `${runningAppInfo.id}-${nodeId}`;
|
||||
const agentModel = getLLMModel(model);
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
const runtimeSubApps = await rewriteSubAppsToolset({
|
||||
subApps: subApps.map<RuntimeNodeItemType>((node) => {
|
||||
const masterMessagesKey = `masterMessages-${nodeId}`;
|
||||
const planMessagesKey = `planMessages-${nodeId}`;
|
||||
|
||||
// Get history messages
|
||||
const { masterHistoryMessages, planHistoryMessages } = (() => {
|
||||
const lastHistory = chatHistories[chatHistories.length - 1];
|
||||
if (lastHistory && lastHistory.obj === ChatRoleEnum.AI) {
|
||||
return {
|
||||
nodeId: node.id,
|
||||
name: node.name,
|
||||
avatar: node.avatar,
|
||||
intro: node.intro,
|
||||
toolDescription: node.toolDescription,
|
||||
flowNodeType: node.flowNodeType,
|
||||
showStatus: node.showStatus,
|
||||
isEntry: false,
|
||||
inputs: node.inputs,
|
||||
outputs: node.outputs,
|
||||
pluginId: node.pluginId,
|
||||
version: node.version,
|
||||
toolConfig: node.toolConfig,
|
||||
catchError: node.catchError
|
||||
masterHistoryMessages: (lastHistory.memories?.[masterMessagesKey] ||
|
||||
[]) as ChatCompletionMessageParam[],
|
||||
planHistoryMessages: (lastHistory.memories?.[planMessagesKey] ||
|
||||
[]) as ChatCompletionMessageParam[]
|
||||
};
|
||||
}),
|
||||
lang
|
||||
});
|
||||
}
|
||||
return {
|
||||
masterHistoryMessages: [],
|
||||
planHistoryMessages: []
|
||||
};
|
||||
})();
|
||||
|
||||
// Check interactive entry
|
||||
props.node.isEntry = false;
|
||||
|
||||
try {
|
||||
const agentModel = getLLMModel(model);
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
// Get files
|
||||
const fileUrlInput = inputs.find((item) => item.key === NodeInputKeyEnum.fileUrlList);
|
||||
if (!fileUrlInput || !fileUrlInput.value || fileUrlInput.value.length === 0) {
|
||||
@@ -138,34 +144,55 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
||||
histories: chatHistories
|
||||
});
|
||||
|
||||
// Get sub apps
|
||||
const runtimeSubApps = await rewriteSubAppsToolset({
|
||||
subApps: subApps.map<RuntimeNodeItemType>((node) => {
|
||||
return {
|
||||
nodeId: node.id,
|
||||
name: node.name,
|
||||
avatar: node.avatar,
|
||||
intro: node.intro,
|
||||
toolDescription: node.toolDescription,
|
||||
flowNodeType: node.flowNodeType,
|
||||
showStatus: node.showStatus,
|
||||
isEntry: false,
|
||||
inputs: node.inputs,
|
||||
outputs: node.outputs,
|
||||
pluginId: node.pluginId,
|
||||
version: node.version,
|
||||
toolConfig: node.toolConfig,
|
||||
catchError: node.catchError
|
||||
};
|
||||
}),
|
||||
lang
|
||||
});
|
||||
|
||||
const subAppList = getSubApps({
|
||||
subApps: runtimeSubApps,
|
||||
addReadFileTool: Object.keys(filesMap).length > 0
|
||||
});
|
||||
|
||||
// Init tool params
|
||||
const toolNodesMap = new Map(runtimeSubApps.map((item) => [item.nodeId, item]));
|
||||
const getToolInfo = (id: string) => {
|
||||
const toolNode = toolNodesMap.get(id) || systemSubInfo[id];
|
||||
const subAppsMap = new Map(runtimeSubApps.map((item) => [item.nodeId, item]));
|
||||
const getSubAppInfo = (id: string) => {
|
||||
const toolNode = subAppsMap.get(id) || systemSubInfo[id];
|
||||
return {
|
||||
name: toolNode?.name || '',
|
||||
avatar: toolNode?.avatar || ''
|
||||
};
|
||||
};
|
||||
|
||||
// Get master request messages
|
||||
const systemMessages = chats2GPTMessages({
|
||||
messages: getSystemPrompt_ChatItemType(systemPrompt || getMasterAgentDefaultPrompt()),
|
||||
reserveId: false
|
||||
});
|
||||
const historyMessages: ChatCompletionMessageParam[] = (() => {
|
||||
const lastHistory = chatHistories[chatHistories.length - 1];
|
||||
if (lastHistory && lastHistory.obj === ChatRoleEnum.AI && lastHistory.memories?.[memoryKey]) {
|
||||
return lastHistory.memories?.[memoryKey];
|
||||
if (masterHistoryMessages && masterHistoryMessages.length > 0) {
|
||||
return masterHistoryMessages;
|
||||
}
|
||||
|
||||
return chats2GPTMessages({ messages: chatHistories, reserveId: false });
|
||||
})();
|
||||
|
||||
const userMessages = chats2GPTMessages({
|
||||
messages: [
|
||||
{
|
||||
@@ -180,8 +207,80 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
||||
});
|
||||
const requestMessages = [...systemMessages, ...historyMessages, ...userMessages];
|
||||
|
||||
// Check interactive entry
|
||||
props.node.isEntry = false;
|
||||
// TODO: 执行 plan function(只有lastInteractive userselect/userInput 时候,才不需要进入 plan)
|
||||
if (lastInteractive?.type !== 'userSelect' && lastInteractive?.type !== 'userInput') {
|
||||
// const planResponse = xxxx
|
||||
// requestMessages.push(一组 toolcall)
|
||||
|
||||
workflowStreamResponse?.({
|
||||
event: SseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({
|
||||
text: '这是 plan'
|
||||
})
|
||||
});
|
||||
|
||||
return {
|
||||
[DispatchNodeResponseKeyEnum.memories]: {
|
||||
[planMessagesKey]: [
|
||||
{
|
||||
role: 'user',
|
||||
content: '测试'
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
content: '测试'
|
||||
}
|
||||
]
|
||||
},
|
||||
// Mock:返回 plan check
|
||||
// [DispatchNodeResponseKeyEnum.interactive]: {
|
||||
// type: 'agentPlanCheck',
|
||||
// params: {}
|
||||
// }
|
||||
|
||||
// Mock: 返回 plan user select
|
||||
// [DispatchNodeResponseKeyEnum.interactive]: {
|
||||
// type: 'agentPlanAskUserSelect',
|
||||
// params: {
|
||||
// description: '测试',
|
||||
// userSelectOptions: [
|
||||
// {
|
||||
// key: 'test',
|
||||
// value: '测试'
|
||||
// },
|
||||
// {
|
||||
// key: 'test2',
|
||||
// value: '测试2'
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// },
|
||||
// Mock: 返回 plan user input
|
||||
// [DispatchNodeResponseKeyEnum.interactive]: {
|
||||
// type: 'agentPlanAskUserForm',
|
||||
// params: {
|
||||
// description: '测试',
|
||||
// inputForm: [
|
||||
// {
|
||||
// type: FlowNodeInputTypeEnum.input,
|
||||
// key: 'test1',
|
||||
// label: '测试1',
|
||||
// value: '',
|
||||
// valueType: WorkflowIOValueTypeEnum.string,
|
||||
// required: true
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// Mock: 返回 plan user query
|
||||
[DispatchNodeResponseKeyEnum.interactive]: {
|
||||
type: 'agentPlanAskQuery',
|
||||
params: {
|
||||
content: '请提供 xxxxx'
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const dispatchFlowResponse: ChatHistoryItemResType[] = [];
|
||||
// console.log(JSON.stringify(requestMessages, null, 2));
|
||||
@@ -200,7 +299,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
||||
|
||||
userKey: externalProvider.openaiAccount,
|
||||
isAborted: res ? () => res.closed : undefined,
|
||||
getToolInfo,
|
||||
getToolInfo: getSubAppInfo,
|
||||
|
||||
onReasoning({ text }) {
|
||||
workflowStreamResponse?.({
|
||||
@@ -219,15 +318,15 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
||||
});
|
||||
},
|
||||
onToolCall({ call }) {
|
||||
const toolNode = getToolInfo(call.function.name);
|
||||
const subApp = getSubAppInfo(call.function.name);
|
||||
workflowStreamResponse?.({
|
||||
id: call.id,
|
||||
event: SseResponseEventEnum.toolCall,
|
||||
data: {
|
||||
tool: {
|
||||
id: `${nodeId}/${call.function.name}`,
|
||||
toolName: toolNode?.name || call.function.name,
|
||||
toolAvatar: toolNode?.avatar || '',
|
||||
toolName: subApp?.name || call.function.name,
|
||||
toolAvatar: subApp?.avatar || '',
|
||||
functionName: call.function.name,
|
||||
params: call.function.arguments ?? ''
|
||||
}
|
||||
@@ -350,7 +449,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
||||
}
|
||||
// User Sub App
|
||||
else {
|
||||
const node = toolNodesMap.get(toolId);
|
||||
const node = subAppsMap.get(toolId);
|
||||
if (!node) {
|
||||
return {
|
||||
response: 'Can not find the tool',
|
||||
@@ -497,7 +596,8 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
|
||||
},
|
||||
// TODO: 需要对 memoryMessages 单独建表存储
|
||||
[DispatchNodeResponseKeyEnum.memories]: {
|
||||
[memoryKey]: filterMemoryMessages(completeMessages)
|
||||
[masterMessagesKey]: filterMemoryMessages(completeMessages),
|
||||
[planMessagesKey]: [] // TODO: plan messages 需要记录
|
||||
},
|
||||
[DispatchNodeResponseKeyEnum.assistantResponses]: previewAssistantResponses,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
|
@@ -72,7 +72,7 @@ export const getSubApps = ({
|
||||
}): ChatCompletionTool[] => {
|
||||
// System Tools: Plan Agent, stop sign, model agent.
|
||||
const systemTools: ChatCompletionTool[] = [
|
||||
PlanAgentTool,
|
||||
// PlanAgentTool,
|
||||
...(addReadFileTool ? [readFileTool] : [])
|
||||
// ModelAgentTool
|
||||
// StopAgentTool,
|
||||
|
@@ -31,6 +31,7 @@ type DispatchPlanAgentResponse = {
|
||||
|
||||
export const dispatchPlanAgent = async ({
|
||||
messages,
|
||||
|
||||
tools,
|
||||
model,
|
||||
customSystemPrompt,
|
||||
|
@@ -23,6 +23,7 @@
|
||||
"config_input_guide": "Set Up Input Guide",
|
||||
"config_input_guide_lexicon": "Set Up Lexicon",
|
||||
"config_input_guide_lexicon_title": "Set Up Lexicon",
|
||||
"confirm_plan_agent": "Please confirm whether the change plan meets expectations. If you need to modify it, you can send the modification requirements in the input box at the bottom.",
|
||||
"content_empty": "No Content",
|
||||
"contextual": "{{num}} Contexts",
|
||||
"contextual_preview": "Contextual Preview {{num}} Items",
|
||||
|
@@ -23,6 +23,7 @@
|
||||
"config_input_guide": "配置输入引导",
|
||||
"config_input_guide_lexicon": "配置词库",
|
||||
"config_input_guide_lexicon_title": "配置词库",
|
||||
"confirm_plan_agent": "请确认改计划是否符合预期,如需修改,可在底部输入框中发送修改要求。",
|
||||
"content_empty": "内容为空",
|
||||
"contextual": "{{num}}条上下文",
|
||||
"contextual_preview": "上下文预览 {{num}} 条",
|
||||
|
@@ -23,6 +23,7 @@
|
||||
"config_input_guide": "設定輸入導引",
|
||||
"config_input_guide_lexicon": "設定詞彙庫",
|
||||
"config_input_guide_lexicon_title": "設定詞彙庫",
|
||||
"confirm_plan_agent": "請確認改計劃是否符合預期,如需修改,可在底部輸入框中發送修改要求。",
|
||||
"content_empty": "無內容",
|
||||
"contextual": "{{num}} 筆上下文",
|
||||
"contextual_preview": "上下文預覽 {{num}} 筆",
|
||||
|
@@ -37,9 +37,9 @@ import { type OutLinkChatAuthProps } from '@fastgpt/global/support/permission/ch
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { ChatRoleEnum, ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import {
|
||||
checkIsInteractiveByHistories,
|
||||
getInteractiveStatus,
|
||||
formatChatValue2InputType,
|
||||
setUserSelectResultToHistories
|
||||
rewriteHistoriesByInteractiveResponse
|
||||
} from './utils';
|
||||
import { ChatTypeEnum, textareaMinH } from './constants';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
@@ -149,7 +149,10 @@ const ChatBox = ({
|
||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||
|
||||
// Workflow running, there are user input or selection
|
||||
const isInteractive = useMemo(() => checkIsInteractiveByHistories(chatRecords), [chatRecords]);
|
||||
const { interactiveType, canSendQuery } = useMemo(
|
||||
() => getInteractiveStatus(chatRecords),
|
||||
[chatRecords]
|
||||
);
|
||||
|
||||
const showExternalVariable = useMemo(() => {
|
||||
const map: Record<string, boolean> = {
|
||||
@@ -338,7 +341,7 @@ const ChatBox = ({
|
||||
})();
|
||||
const updateValue: AIChatItemValueItemType = cloneDeep(item.value[updateIndex]);
|
||||
updateValue.id = responseValueId;
|
||||
console.log(event, tool, updateValue);
|
||||
|
||||
if (event === SseResponseEventEnum.flowNodeResponse && nodeResponse) {
|
||||
return {
|
||||
...item,
|
||||
@@ -544,8 +547,8 @@ const ChatBox = ({
|
||||
text = '',
|
||||
files = [],
|
||||
history = chatRecords,
|
||||
interactiveType,
|
||||
autoTTSResponse = false,
|
||||
isInteractivePrompt = false,
|
||||
hideInUI = false
|
||||
}) => {
|
||||
variablesForm.handleSubmit(
|
||||
@@ -638,9 +641,13 @@ const ChatBox = ({
|
||||
|
||||
// Update histories(Interactive input does not require new session rounds)
|
||||
setChatRecords(
|
||||
isInteractivePrompt
|
||||
interactiveType
|
||||
? // 把交互的结果存储到对话记录中,交互模式下,不需要新的会话轮次
|
||||
setUserSelectResultToHistories(newChatList.slice(0, -2), text)
|
||||
rewriteHistoriesByInteractiveResponse({
|
||||
histories: newChatList,
|
||||
interactiveType: interactiveType,
|
||||
interactiveVal: text
|
||||
})
|
||||
: newChatList
|
||||
);
|
||||
|
||||
@@ -698,7 +705,7 @@ const ChatBox = ({
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
if (!checkIsInteractiveByHistories(newChatHistories)) {
|
||||
if (!getInteractiveStatus(newChatHistories).interactiveType) {
|
||||
createQuestionGuide();
|
||||
}
|
||||
|
||||
@@ -982,7 +989,7 @@ const ChatBox = ({
|
||||
abortRequest('leave');
|
||||
}, [chatId, appId, abortRequest, setValue]);
|
||||
|
||||
const canSendPrompt = onStartChat && chatStarted && active && !isInteractive;
|
||||
const canSendPrompt = onStartChat && chatStarted && active && canSendQuery;
|
||||
|
||||
// Add listener
|
||||
useEffect(() => {
|
||||
@@ -995,9 +1002,12 @@ const ChatBox = ({
|
||||
};
|
||||
window.addEventListener('message', windowMessage);
|
||||
|
||||
const fn: SendPromptFnType = (e) => {
|
||||
if (canSendPrompt || e.isInteractivePrompt) {
|
||||
sendPrompt(e);
|
||||
const fn = ({ focus = false, ...e }: ChatBoxInputType & { focus?: boolean }) => {
|
||||
if (canSendPrompt || focus) {
|
||||
sendPrompt({
|
||||
...e,
|
||||
interactiveType
|
||||
});
|
||||
}
|
||||
};
|
||||
eventBus.on(EventNameEnum.sendQuestion, fn);
|
||||
@@ -1011,7 +1021,7 @@ const ChatBox = ({
|
||||
eventBus.off(EventNameEnum.sendQuestion);
|
||||
eventBus.off(EventNameEnum.editQuestion);
|
||||
};
|
||||
}, [isReady, resetInputVal, sendPrompt, canSendPrompt]);
|
||||
}, [isReady, resetInputVal, sendPrompt, canSendPrompt, interactiveType]);
|
||||
|
||||
// Auto send prompt
|
||||
useDebounceEffect(
|
||||
|
@@ -3,6 +3,7 @@ import type { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import type { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { ChatItemValueItemType, ToolModuleResponseItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import type { InteractiveNodeResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
|
||||
export type UserInputFileItemType = {
|
||||
id: string;
|
||||
@@ -25,7 +26,7 @@ export type ChatBoxInputFormType = {
|
||||
export type ChatBoxInputType = {
|
||||
text?: string;
|
||||
files?: UserInputFileItemType[];
|
||||
isInteractivePrompt?: boolean;
|
||||
interactiveType?: InteractiveNodeResponseType['type'];
|
||||
hideInUI?: boolean;
|
||||
};
|
||||
|
||||
|
@@ -7,6 +7,8 @@ import { type ChatBoxInputType, type UserInputFileItemType } from './type';
|
||||
import { getFileIcon } from '@fastgpt/global/common/file/icon';
|
||||
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import type { InteractiveNodeResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import { ConfirmPlanAgentText } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
|
||||
export const formatChatValue2InputType = (value?: ChatItemValueItemType[]): ChatBoxInputType => {
|
||||
if (!value) {
|
||||
@@ -43,36 +45,83 @@ export const formatChatValue2InputType = (value?: ChatItemValueItemType[]): Chat
|
||||
};
|
||||
};
|
||||
|
||||
export const checkIsInteractiveByHistories = (chatHistories: ChatSiteItemType[]) => {
|
||||
export const getInteractiveStatus = (
|
||||
chatHistories: ChatSiteItemType[]
|
||||
): {
|
||||
interactiveType: InteractiveNodeResponseType['type'] | undefined;
|
||||
canSendQuery: boolean;
|
||||
} => {
|
||||
const lastAIHistory = chatHistories[chatHistories.length - 1];
|
||||
if (!lastAIHistory) return false;
|
||||
if (!lastAIHistory)
|
||||
return {
|
||||
interactiveType: undefined,
|
||||
canSendQuery: true
|
||||
};
|
||||
|
||||
const lastMessageValue = lastAIHistory.value[
|
||||
lastAIHistory.value.length - 1
|
||||
] as AIChatItemValueItemType;
|
||||
|
||||
if (lastMessageValue && !!lastMessageValue?.interactive?.params) {
|
||||
const params = lastMessageValue.interactive.params;
|
||||
// 如果用户选择了,则不认为是交互模式(可能是上一轮以交互结尾,发起的新的一轮对话)
|
||||
if ('userSelectOptions' in params) {
|
||||
return !params.userSelectedVal;
|
||||
} else if ('inputForm' in params) {
|
||||
return !params.submitted;
|
||||
if (!lastMessageValue || !lastMessageValue.interactive) {
|
||||
return {
|
||||
interactiveType: undefined,
|
||||
canSendQuery: true
|
||||
};
|
||||
}
|
||||
|
||||
const interactive = lastMessageValue.interactive;
|
||||
|
||||
if (interactive.params) {
|
||||
if (interactive.type === 'userSelect' || interactive.type === 'agentPlanAskUserSelect') {
|
||||
return {
|
||||
interactiveType: !!interactive.params.userSelectedVal ? undefined : 'userSelect',
|
||||
canSendQuery: !!interactive.params.userSelectedVal
|
||||
};
|
||||
}
|
||||
if (interactive.type === 'userInput' || interactive.type === 'agentPlanAskUserForm') {
|
||||
return {
|
||||
interactiveType: !!interactive.params.submitted ? undefined : 'userInput',
|
||||
canSendQuery: !!interactive.params.submitted
|
||||
};
|
||||
}
|
||||
if (interactive.type === 'agentPlanCheck') {
|
||||
return {
|
||||
interactiveType: !!interactive.params.confirmed ? undefined : 'agentPlanCheck',
|
||||
canSendQuery: true
|
||||
};
|
||||
}
|
||||
if (interactive.type === 'agentPlanAskQuery') {
|
||||
return {
|
||||
interactiveType: 'agentPlanAskQuery',
|
||||
canSendQuery: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return {
|
||||
interactiveType: undefined,
|
||||
canSendQuery: true
|
||||
};
|
||||
};
|
||||
|
||||
export const setUserSelectResultToHistories = (
|
||||
histories: ChatSiteItemType[],
|
||||
interactiveVal: string
|
||||
): ChatSiteItemType[] => {
|
||||
if (histories.length === 0) return histories;
|
||||
export const rewriteHistoriesByInteractiveResponse = ({
|
||||
histories,
|
||||
interactiveVal,
|
||||
interactiveType
|
||||
}: {
|
||||
histories: ChatSiteItemType[];
|
||||
interactiveVal: string;
|
||||
interactiveType: InteractiveNodeResponseType['type'];
|
||||
}): ChatSiteItemType[] => {
|
||||
const formatHistories = (() => {
|
||||
if (interactiveType === 'agentPlanCheck' && interactiveVal !== ConfirmPlanAgentText) {
|
||||
return histories;
|
||||
}
|
||||
return histories.slice(0, -2);
|
||||
})();
|
||||
|
||||
// @ts-ignore
|
||||
return histories.map((item, i) => {
|
||||
if (i !== histories.length - 1) return item;
|
||||
const newHistories = formatHistories.map((item, i) => {
|
||||
if (i !== formatHistories.length - 1) return item;
|
||||
|
||||
const value = item.value.map((val, i) => {
|
||||
if (i !== item.value.length - 1) {
|
||||
@@ -81,7 +130,10 @@ export const setUserSelectResultToHistories = (
|
||||
if (!('interactive' in val) || !val.interactive) return val;
|
||||
|
||||
const finalInteractive = extractDeepestInteractive(val.interactive);
|
||||
if (finalInteractive.type === 'userSelect') {
|
||||
if (
|
||||
finalInteractive.type === 'userSelect' ||
|
||||
finalInteractive.type === 'agentPlanAskUserSelect'
|
||||
) {
|
||||
return {
|
||||
...val,
|
||||
interactive: {
|
||||
@@ -96,7 +148,10 @@ export const setUserSelectResultToHistories = (
|
||||
};
|
||||
}
|
||||
|
||||
if (finalInteractive.type === 'userInput') {
|
||||
if (
|
||||
finalInteractive.type === 'userInput' ||
|
||||
finalInteractive.type === 'agentPlanAskUserForm'
|
||||
) {
|
||||
return {
|
||||
...val,
|
||||
interactive: {
|
||||
@@ -108,12 +163,28 @@ export const setUserSelectResultToHistories = (
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (finalInteractive.type === 'agentPlanCheck') {
|
||||
return {
|
||||
...val,
|
||||
interactive: {
|
||||
...finalInteractive,
|
||||
params: {
|
||||
confirmed: true
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return val;
|
||||
});
|
||||
|
||||
return {
|
||||
...item,
|
||||
status: ChatStatusEnum.loading,
|
||||
value
|
||||
};
|
||||
} as ChatSiteItemType;
|
||||
});
|
||||
|
||||
return newHistories;
|
||||
};
|
||||
|
@@ -19,19 +19,25 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import type {
|
||||
InteractiveBasicType,
|
||||
InteractiveNodeResponseType,
|
||||
UserInputInteractive,
|
||||
UserSelectInteractive
|
||||
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
|
||||
import { SelectOptionsComponent, FormInputComponent } from './Interactive/InteractiveComponents';
|
||||
import {
|
||||
SelectOptionsComponent,
|
||||
FormInputComponent,
|
||||
AgentPlanCheckComponent
|
||||
} from './Interactive/InteractiveComponents';
|
||||
import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { type OnOpenCiteModalProps } from '@/web/core/chat/context/chatItemContext';
|
||||
import { ChatBoxContext } from '../ChatContainer/ChatBox/Provider';
|
||||
import { useCreation } from 'ahooks';
|
||||
import { useSafeTranslation } from '@fastgpt/web/hooks/useSafeTranslation';
|
||||
import { ConfirmPlanAgentText } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
|
||||
const accordionButtonStyle = {
|
||||
w: 'auto',
|
||||
@@ -215,21 +221,21 @@ ${response}`}
|
||||
(prevProps, nextProps) => isEqual(prevProps, nextProps)
|
||||
);
|
||||
|
||||
const onSendPrompt = (e: { text: string; isInteractivePrompt: boolean }) =>
|
||||
eventBus.emit(EventNameEnum.sendQuestion, e);
|
||||
const onSendPrompt = (text: string) =>
|
||||
eventBus.emit(EventNameEnum.sendQuestion, {
|
||||
text,
|
||||
focus: true
|
||||
});
|
||||
const RenderUserSelectInteractive = React.memo(function RenderInteractive({
|
||||
interactive
|
||||
}: {
|
||||
interactive: InteractiveBasicType & UserSelectInteractive;
|
||||
interactive: UserSelectInteractive;
|
||||
}) {
|
||||
return (
|
||||
<SelectOptionsComponent
|
||||
interactiveParams={interactive.params}
|
||||
onSelect={(value) => {
|
||||
onSendPrompt({
|
||||
text: value,
|
||||
isInteractivePrompt: true
|
||||
});
|
||||
onSendPrompt(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -237,7 +243,7 @@ const RenderUserSelectInteractive = React.memo(function RenderInteractive({
|
||||
const RenderUserFormInteractive = React.memo(function RenderFormInput({
|
||||
interactive
|
||||
}: {
|
||||
interactive: InteractiveBasicType & UserInputInteractive;
|
||||
interactive: UserInputInteractive;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -261,10 +267,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
|
||||
}
|
||||
});
|
||||
|
||||
onSendPrompt({
|
||||
text: JSON.stringify(finalData),
|
||||
isInteractivePrompt: true
|
||||
});
|
||||
onSendPrompt(JSON.stringify(finalData));
|
||||
},
|
||||
[interactive.params.inputForm]
|
||||
);
|
||||
@@ -329,12 +332,25 @@ const AIResponseBox = ({
|
||||
);
|
||||
}
|
||||
if ('interactive' in value && value.interactive) {
|
||||
const finalInteractive = extractDeepestInteractive(value.interactive);
|
||||
if (finalInteractive.type === 'userSelect') {
|
||||
return <RenderUserSelectInteractive interactive={finalInteractive} />;
|
||||
const interactive = extractDeepestInteractive(value.interactive);
|
||||
if (interactive.type === 'userSelect' || interactive.type === 'agentPlanAskUserSelect') {
|
||||
return <RenderUserSelectInteractive interactive={interactive} />;
|
||||
}
|
||||
if (finalInteractive.type === 'userInput') {
|
||||
return <RenderUserFormInteractive interactive={finalInteractive} />;
|
||||
if (interactive.type === 'userInput' || interactive.type === 'agentPlanAskUserForm') {
|
||||
return <RenderUserFormInteractive interactive={interactive} />;
|
||||
}
|
||||
if (interactive.type === 'agentPlanCheck') {
|
||||
return (
|
||||
<AgentPlanCheckComponent
|
||||
interactiveParams={interactive.params}
|
||||
onConfirm={() => {
|
||||
onSendPrompt(ConfirmPlanAgentText);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (interactive.type === 'agentPlanAskQuery') {
|
||||
return <Box>{interactive.params.content}</Box>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,16 +3,18 @@ import { Box, Flex } from '@chakra-ui/react';
|
||||
import { Controller, useForm, type UseFormHandleSubmit } from 'react-hook-form';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import {
|
||||
type UserInputFormItemType,
|
||||
type UserInputInteractive,
|
||||
type UserSelectInteractive,
|
||||
type UserSelectOptionItemType
|
||||
import type {
|
||||
AgentPlanCheckInteractive,
|
||||
UserInputFormItemType,
|
||||
UserInputInteractive,
|
||||
UserSelectInteractive,
|
||||
UserSelectOptionItemType
|
||||
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import InputRender from '@/components/core/app/formRender';
|
||||
import { nodeInputTypeToInputType } from '@/components/core/app/formRender/utils';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import LeftRadio from '@fastgpt/web/components/common/Radio/LeftRadio';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const DescriptionBox = React.memo(function DescriptionBox({
|
||||
description
|
||||
@@ -133,3 +135,23 @@ export const FormInputComponent = React.memo(function FormInputComponent({
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
// Agent interactive
|
||||
export const AgentPlanCheckComponent = React.memo(function AgentPlanCheckComponent({
|
||||
interactiveParams,
|
||||
onConfirm
|
||||
}: {
|
||||
interactiveParams: AgentPlanCheckInteractive['params'];
|
||||
onConfirm: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return interactiveParams?.confirmed ? (
|
||||
// TODO:临时 UI
|
||||
<Box>已确认计划</Box>
|
||||
) : (
|
||||
<Box>
|
||||
<Box>{t('chat:confirm_plan_agent')}</Box>
|
||||
<Button onClick={onConfirm}>{t('common:Confirm')}</Button>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
@@ -139,7 +139,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
const newHistories = concatHistories(histories, chatMessages);
|
||||
const interactive = getLastInteractiveValue(newHistories) || undefined;
|
||||
const interactive = getLastInteractiveValue(newHistories);
|
||||
// Get runtimeNodes
|
||||
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, interactive));
|
||||
if (isPlugin) {
|
||||
|
@@ -319,8 +319,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
return ChatSourceEnum.online;
|
||||
})();
|
||||
|
||||
const isInteractiveRequest = !!getLastInteractiveValue(histories);
|
||||
|
||||
const newTitle = isPlugin
|
||||
? variables.cTime ?? formatTime2YMDHM()
|
||||
: getChatTitleFromChatMessage(userQuestion);
|
||||
@@ -333,6 +331,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
memories: system_memories
|
||||
};
|
||||
|
||||
const isInteractiveRequest = !!getLastInteractiveValue(histories);
|
||||
const saveChatId = chatId || getNanoid(24);
|
||||
const params = {
|
||||
chatId: saveChatId,
|
||||
|
Reference in New Issue
Block a user