Skip to content
Closed
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
53 changes: 33 additions & 20 deletions packages/service/core/workflow/dispatch/interactive/formInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { UserInputFormItemType } from '@fastgpt/global/core/workflow/templa
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { anyValueDecrypt } from '../../../../common/secret/utils';
import { getLogger, LogCategories } from '../../../../common/logger';
import { getS3ChatSource } from '../../../../common/s3/sources/chat';

const logger = getLogger(LogCategories.MODULE.WORKFLOW.INTERACTIVE);

Expand Down Expand Up @@ -60,31 +61,43 @@ export const dispatchFormInput = async (props: Props): Promise<FormInputResponse
}
})();

const userInputVal = Object.entries(rawUserInputVal).reduce(
(acc, [key, value]) => {
const inputConfig = userInputForms.find((form) => form.key === key);
const userInputVal: Record<string, any> = {};
for (const [key, value] of Object.entries(rawUserInputVal)) {
const inputConfig = userInputForms.find((form) => form.key === key);

if (inputConfig?.type === FlowNodeInputTypeEnum.password) {
acc[key] = anyValueDecrypt(value);
} else if (inputConfig?.type === FlowNodeInputTypeEnum.fileSelect) {
if (Array.isArray(value)) {
acc[key] = value.map((file: any) => {
if (typeof file === 'object' && file.url) {
if (inputConfig?.type === FlowNodeInputTypeEnum.password) {
userInputVal[key] = anyValueDecrypt(value);
} else if (inputConfig?.type === FlowNodeInputTypeEnum.fileSelect) {
if (Array.isArray(value)) {
const files = await Promise.all(
value.map(async (file: any) => {
if (typeof file === 'string' && file) {
return file;
}

if (!file || typeof file !== 'object') return;

if (typeof file.key === 'string' && file.key) {
const { url } = await getS3ChatSource().createGetChatFileURL({
key: file.key,
external: true
});
return url;
}

if (typeof file.url === 'string' && file.url) {
return file.url;
}
return file;
});
} else {
acc[key] = value;
}
})
);
userInputVal[key] = files.filter((file) => typeof file === 'string' && file);
} else {
acc[key] = value;
userInputVal[key] = typeof value === 'string' && value ? [value] : [];
}

return acc;
},
{} as Record<string, any>
);
} else {
userInputVal[key] = value;
}
}

return {
data: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import { dispatchFormInput } from '@fastgpt/service/core/workflow/dispatch/interactive/formInput';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import {
NodeInputKeyEnum,
NodeOutputKeyEnum,
WorkflowIOValueTypeEnum
} from '@fastgpt/global/core/workflow/constants';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';

const mocks = vi.hoisted(() => ({
createGetChatFileURL: vi.fn()
}));

vi.mock('@fastgpt/service/common/s3/sources/chat', () => ({
getS3ChatSource: () => ({
createGetChatFileURL: mocks.createGetChatFileURL
})
}));

const buildProps = (text: string) =>
({
histories: [{ id: 'h1' }, { id: 'h2' }],
node: {
isEntry: true
},
params: {
[NodeInputKeyEnum.description]: '',
[NodeInputKeyEnum.userInputForms]: [
{
key: 'files',
label: 'Files',
type: FlowNodeInputTypeEnum.fileSelect,
valueType: WorkflowIOValueTypeEnum.arrayAny,
value: [],
required: false
}
]
},
query: [
{
text: {
content: text
}
}
],
lastInteractive: {
type: 'userInput'
}
}) as any;

describe('dispatchFormInput', () => {
afterEach(() => {
vi.restoreAllMocks();
});

it('converts fileSelect keys to urls and filters invalid file objects', async () => {
mocks.createGetChatFileURL.mockImplementation(async ({ key }: { key: string }) => ({
url: `https://files.example/${key}`
}));

const result = await dispatchFormInput(
buildProps(
JSON.stringify({
files: [
{ key: 'chat/app/user/chat/file.png' },
{ url: 'https://example.com/file.pdf' },
{ rawFile: {}, icon: 'data:image/png;base64,large' },
null,
{},
''
]
})
)
);

expect(mocks.createGetChatFileURL).toHaveBeenCalledWith({
key: 'chat/app/user/chat/file.png',
external: true
});
expect(result.data?.files).toEqual([
'https://files.example/chat/app/user/chat/file.png',
'https://example.com/file.pdf'
]);
expect(result.data?.[NodeOutputKeyEnum.formInputResult]).toEqual({
files: ['https://files.example/chat/app/user/chat/file.png', 'https://example.com/file.pdf']
});
});

it('returns an interactive form when the node is not ready to consume input', async () => {
const props = buildProps('{}');
props.node.isEntry = false;

const result = await dispatchFormInput(props);

expect(result[DispatchNodeResponseKeyEnum.interactive]).toEqual({
type: 'userInput',
params: {
description: '',
inputForm: props.params[NodeInputKeyEnum.userInputForms]
}
});
expect(props.node.isEntry).toBe(false);
});

it('does not pass non-array fileSelect objects through', async () => {
const result = await dispatchFormInput(
buildProps(
JSON.stringify({
files: { icon: 'data:image/png;base64,large' }
})
)
);

expect(result.data?.files).toEqual([]);
});
});
1 change: 1 addition & 0 deletions packages/web/i18n/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@
"core.chat.Unpin": "Unpin",
"core.chat.error.Chat error": "Chat Error",
"core.chat.error.Messages empty": "API Content is Empty, Possibly Due to Text Being Too Long",
"core.chat.error.Request too large": "The debug request is too large. Reduce this input or split the file and try again.",
"core.chat.error.Select dataset empty": "You Have Not Selected a Dataset",
"core.chat.error.data_error": "Data Retrieval Error",
"core.chat.feedback.No Content": "User Did Not Provide Specific Feedback Content",
Expand Down
1 change: 1 addition & 0 deletions packages/web/i18n/zh-CN/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@
"core.chat.Unpin": "取消置顶",
"core.chat.error.Chat error": "对话出现异常",
"core.chat.error.Messages empty": "接口内容为空,可能文本超长了~",
"core.chat.error.Request too large": "调试请求内容过大,请减少本次输入内容或拆分文件后重试",
"core.chat.error.Select dataset empty": "你没有选择知识库",
"core.chat.error.data_error": "获取数据异常",
"core.chat.feedback.No Content": "用户没有填写具体反馈内容",
Expand Down
1 change: 1 addition & 0 deletions packages/web/i18n/zh-Hant/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@
"core.chat.Unpin": "取消釘選",
"core.chat.error.Chat error": "對話發生錯誤",
"core.chat.error.Messages empty": "API 內容為空,可能是文字過長",
"core.chat.error.Request too large": "調試請求內容過大,請減少本次輸入內容或拆分檔案後重試",
"core.chat.error.Select dataset empty": "您尚未選擇知識庫",
"core.chat.error.data_error": "取得資料錯誤",
"core.chat.feedback.No Content": "使用者未提供具體回饋內容",
Expand Down
2 changes: 1 addition & 1 deletion pro
Submodule pro updated from ee1b1d to 469721
Loading
Loading