mirror of
https://github.com/labring/FastGPT.git
synced 2026-05-02 01:02:05 +08:00
fix: plugin file selector (#5871)
* fix: plugin file selector * fix: render * fix: upload * fix: file selector auth --------- Co-authored-by: archer <545436317@qq.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { ObjectIdSchema } from '@fastgpt/global/common/type/mongo';
|
|||||||
|
|
||||||
export const ChatFileUploadSchema = z.object({
|
export const ChatFileUploadSchema = z.object({
|
||||||
appId: ObjectIdSchema,
|
appId: ObjectIdSchema,
|
||||||
chatId: z.string().length(24),
|
chatId: z.string().nonempty(),
|
||||||
uId: z.string().nonempty(),
|
uId: z.string().nonempty(),
|
||||||
filename: z.string().nonempty()
|
filename: z.string().nonempty()
|
||||||
});
|
});
|
||||||
@@ -11,7 +11,7 @@ export type CheckChatFileKeys = z.infer<typeof ChatFileUploadSchema>;
|
|||||||
|
|
||||||
export const DelChatFileByPrefixSchema = z.object({
|
export const DelChatFileByPrefixSchema = z.object({
|
||||||
appId: ObjectIdSchema,
|
appId: ObjectIdSchema,
|
||||||
chatId: z.string().length(24).optional(),
|
chatId: z.string().nonempty().optional(),
|
||||||
uId: z.string().nonempty().optional()
|
uId: z.string().nonempty().optional()
|
||||||
});
|
});
|
||||||
export type DelChatFileByPrefixParams = z.infer<typeof DelChatFileByPrefixSchema>;
|
export type DelChatFileByPrefixParams = z.infer<typeof DelChatFileByPrefixSchema>;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { DragEvent } from 'react';
|
import type { DragEvent } from 'react';
|
||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import type { UserInputFileItemType } from '../../chat/ChatContainer/ChatBox/type';
|
import type { UserInputFileItemType } from '../../chat/ChatContainer/ChatBox/type';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -31,8 +31,9 @@ import { ChatBoxContext } from '../../chat/ChatContainer/ChatBox/Provider';
|
|||||||
import { POST } from '@/web/common/api/request';
|
import { POST } from '@/web/common/api/request';
|
||||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { useDebounceEffect } from 'ahooks';
|
|
||||||
import { formatFileSize, parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
import { formatFileSize, parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
||||||
|
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||||
|
import { PluginRunContext } from '../../chat/ChatContainer/PluginRunBox/context';
|
||||||
|
|
||||||
const FileSelector = ({
|
const FileSelector = ({
|
||||||
fileUrls,
|
fileUrls,
|
||||||
@@ -45,49 +46,86 @@ const FileSelector = ({
|
|||||||
canSelectCustomFileExtension,
|
canSelectCustomFileExtension,
|
||||||
customFileExtensionList,
|
customFileExtensionList,
|
||||||
canLocalUpload,
|
canLocalUpload,
|
||||||
canUrlUpload
|
canUrlUpload,
|
||||||
|
isDisabled = false
|
||||||
}: AppFileSelectConfigType & {
|
}: AppFileSelectConfigType & {
|
||||||
fileUrls: string[];
|
fileUrls: string[] | any[]; // Can be string[] or file object[]
|
||||||
onChange: (e: string[]) => void;
|
onChange: (e: any[]) => void;
|
||||||
canLocalUpload?: boolean;
|
canLocalUpload?: boolean;
|
||||||
canUrlUpload?: boolean;
|
canUrlUpload?: boolean;
|
||||||
|
isDisabled?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { feConfigs } = useSystemStore();
|
const { feConfigs } = useSystemStore();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
const chatBoxOutLinkAuthData = useContextSelector(ChatBoxContext, (v) => v?.outLinkAuthData);
|
||||||
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
|
const chatBoxAppId = useContextSelector(ChatBoxContext, (v) => v?.appId);
|
||||||
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
|
const chatBoxChatId = useContextSelector(ChatBoxContext, (v) => v?.chatId);
|
||||||
|
|
||||||
|
const pluginOutLinkAuthData = useContextSelector(PluginRunContext, (v) => v?.outLinkAuthData);
|
||||||
|
const pluginAppId = useContextSelector(PluginRunContext, (v) => v?.appId);
|
||||||
|
const pluginChatId = useContextSelector(PluginRunContext, (v) => v?.chatId);
|
||||||
|
|
||||||
|
const chatItemAppId = useContextSelector(ChatItemContext, (v) => v?.chatBoxData?.appId);
|
||||||
|
const chatItemChatId = useContextSelector(ChatItemContext, (v) => v?.chatBoxData?.chatId);
|
||||||
|
|
||||||
|
const outLinkAuthData = useMemo(
|
||||||
|
() => ({
|
||||||
|
...(chatBoxOutLinkAuthData || {}),
|
||||||
|
...(pluginOutLinkAuthData || {})
|
||||||
|
}),
|
||||||
|
[chatBoxOutLinkAuthData, pluginOutLinkAuthData]
|
||||||
|
);
|
||||||
|
const appId = useMemo(
|
||||||
|
() => chatBoxAppId || pluginAppId || chatItemAppId || '',
|
||||||
|
[chatBoxAppId, pluginAppId, chatItemAppId]
|
||||||
|
);
|
||||||
|
const chatId = useMemo(
|
||||||
|
() => chatBoxChatId || pluginChatId || chatItemChatId || '',
|
||||||
|
[chatBoxChatId, pluginChatId, chatItemChatId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [cloneFiles, setCloneFiles] = useState<UserInputFileItemType[]>(() => {
|
||||||
|
return fileUrls
|
||||||
|
.map((item) => {
|
||||||
|
const url = typeof item === 'string' ? item : item?.url || item?.key;
|
||||||
|
const key = typeof item === 'string' ? undefined : item?.key;
|
||||||
|
const name = typeof item === 'string' ? undefined : item?.name;
|
||||||
|
const type = typeof item === 'string' ? undefined : item?.type;
|
||||||
|
|
||||||
|
if (!url) return null as unknown as UserInputFileItemType;
|
||||||
|
|
||||||
const [cloneFiles, setCloneFiles] = useState<UserInputFileItemType[]>(
|
|
||||||
fileUrls
|
|
||||||
.map((url) => {
|
|
||||||
const fileType = parseUrlToFileType(url);
|
const fileType = parseUrlToFileType(url);
|
||||||
if (!fileType) return null as unknown as UserInputFileItemType;
|
if (!fileType && !type) return null as unknown as UserInputFileItemType;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: getNanoid(6),
|
id: getNanoid(6),
|
||||||
name: fileType.name || url,
|
name: name || fileType?.name || url,
|
||||||
type: fileType.type,
|
type: type || fileType?.type || ChatFileTypeEnum.file,
|
||||||
icon: getFileIcon(fileType.name || url),
|
icon: getFileIcon(name || fileType?.name || url),
|
||||||
url: fileType.url,
|
url: typeof item === 'string' ? fileType?.url : item?.url,
|
||||||
status: 1,
|
status: 1,
|
||||||
key: url.startsWith('chat/') ? url : undefined
|
key: key || (typeof item === 'string' && url.startsWith('chat/') ? url : undefined)
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(Boolean) as UserInputFileItemType[]
|
.filter(Boolean) as UserInputFileItemType[];
|
||||||
);
|
});
|
||||||
// 采用异步更新顶层的方式
|
|
||||||
useDebounceEffect(
|
useEffect(() => {
|
||||||
() => {
|
const fileObjects = cloneFiles
|
||||||
onChange(cloneFiles.map((file) => file.key || file.url || '').filter(Boolean));
|
.filter((file) => file.url || file.key)
|
||||||
},
|
.map((file) => {
|
||||||
[cloneFiles],
|
const fileObj = {
|
||||||
{
|
type: file.type,
|
||||||
wait: 1000
|
name: file.name,
|
||||||
}
|
key: file.key,
|
||||||
);
|
url: file.url || file.key || ''
|
||||||
|
};
|
||||||
|
return fileObj;
|
||||||
|
});
|
||||||
|
onChange(fileObjects as any);
|
||||||
|
}, [cloneFiles, onChange]);
|
||||||
|
|
||||||
const fileType = useMemo(() => {
|
const fileType = useMemo(() => {
|
||||||
return getUploadFileType({
|
return getUploadFileType({
|
||||||
@@ -378,9 +416,10 @@ const FileSelector = ({
|
|||||||
borderColor={'myGray.250'}
|
borderColor={'myGray.250'}
|
||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
userSelect={'none'}
|
userSelect={'none'}
|
||||||
{...(isMaxSelected
|
{...(isMaxSelected || isDisabled
|
||||||
? {
|
? {
|
||||||
cursor: 'not-allowed'
|
cursor: 'not-allowed',
|
||||||
|
opacity: isDisabled ? 0.6 : 1
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
@@ -397,10 +436,10 @@ const FileSelector = ({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<MyIcon name={'common/uploadFileFill'} w={'32px'} />
|
<MyIcon name={'common/uploadFileFill'} w={'32px'} />
|
||||||
{isMaxSelected ? (
|
{isMaxSelected || isDisabled ? (
|
||||||
<>
|
<>
|
||||||
<Box fontWeight={'500'} fontSize={'sm'}>
|
<Box fontWeight={'500'} fontSize={'sm'}>
|
||||||
{t('file:reached_max_file_count')}
|
{isDisabled ? t('common:Running') : t('file:reached_max_file_count')}
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -427,7 +466,7 @@ const FileSelector = ({
|
|||||||
zIndex={10}
|
zIndex={10}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
isDisabled={isMaxSelected}
|
isDisabled={isMaxSelected || isDisabled}
|
||||||
value={urlInput}
|
value={urlInput}
|
||||||
onChange={(e) => setUrlInput(e.target.value)}
|
onChange={(e) => setUrlInput(e.target.value)}
|
||||||
onBlur={(e) => handleAddUrl(e.target.value)}
|
onBlur={(e) => handleAddUrl(e.target.value)}
|
||||||
@@ -437,7 +476,11 @@ const FileSelector = ({
|
|||||||
pl={8}
|
pl={8}
|
||||||
py={1.5}
|
py={1.5}
|
||||||
placeholder={
|
placeholder={
|
||||||
isMaxSelected ? t('file:reached_max_file_count') : t('chat:click_to_add_url')
|
isDisabled
|
||||||
|
? t('common:Running')
|
||||||
|
: isMaxSelected
|
||||||
|
? t('file:reached_max_file_count')
|
||||||
|
: t('chat:click_to_add_url')
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
@@ -476,6 +519,7 @@ const FileSelector = ({
|
|||||||
aria-label={'Delete file'}
|
aria-label={'Delete file'}
|
||||||
icon={<MyIcon name={'close'} w={'1rem'} />}
|
icon={<MyIcon name={'close'} w={'1rem'} />}
|
||||||
onClick={() => handleDeleteFile(file.id)}
|
onClick={() => handleDeleteFile(file.id)}
|
||||||
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<HStack w={'24px'} h={'24px'} justifyContent={'center'}>
|
<HStack w={'24px'} h={'24px'} justifyContent={'center'}>
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ const InputRender = (props: InputRenderProps) => {
|
|||||||
customFileExtensionList={props.customFileExtensionList}
|
customFileExtensionList={props.customFileExtensionList}
|
||||||
canLocalUpload={props.canLocalUpload}
|
canLocalUpload={props.canLocalUpload}
|
||||||
canUrlUpload={props.canUrlUpload}
|
canUrlUpload={props.canUrlUpload}
|
||||||
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -282,6 +282,7 @@ const RenderInput = () => {
|
|||||||
fieldName={inputKey}
|
fieldName={inputKey}
|
||||||
modelList={llmModelList}
|
modelList={llmModelList}
|
||||||
isRichText={false}
|
isRichText={false}
|
||||||
|
canLocalUpload={input.canLocalUpload ?? true}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
+4
-1
@@ -25,7 +25,9 @@ export const defaultInput: FlowNodeInputItemType = {
|
|||||||
list: [{ label: '', value: '' }],
|
list: [{ label: '', value: '' }],
|
||||||
maxFiles: 5,
|
maxFiles: 5,
|
||||||
canSelectFile: true,
|
canSelectFile: true,
|
||||||
canSelectImg: true
|
canSelectImg: true,
|
||||||
|
canLocalUpload: true,
|
||||||
|
canUrlUpload: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const FieldEditModal = ({
|
const FieldEditModal = ({
|
||||||
@@ -153,6 +155,7 @@ const FieldEditModal = ({
|
|||||||
|
|
||||||
const onSubmitSuccess = useCallback(
|
const onSubmitSuccess = useCallback(
|
||||||
(data: FlowNodeInputItemType, action: 'confirm' | 'continue') => {
|
(data: FlowNodeInputItemType, action: 'confirm' | 'continue') => {
|
||||||
|
console.log('data', data);
|
||||||
data.label = data?.label?.trim();
|
data.label = data?.label?.trim();
|
||||||
|
|
||||||
if (!data.label) {
|
if (!data.label) {
|
||||||
|
|||||||
Reference in New Issue
Block a user