mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +02:00
⬆️ feat: Cancel chat file uploads; fix: Assistant uploads (#4433)
* refactor: move file mutations to dedicated file, improve typing * refactor(ChatForm): utilize FileFormWrapper to consolidate file upload logic/rendering to single parent * refactor: better TSX heirarchies between AttachFile and FileFormWrapper * refactor: `abortUpload` WIP * fix: file debugging and file upload issues * refactor: reject promise outright if axios intercepted error does not include response property * chore: bump data-provider version to 0.7.428 * refactor: Add return type to localize function in Translation.ts * refactor: allow message file attachment upload request cancellations, and add localizations for file upload errors * refactor: include Azure OpenAI in paramEndpoints set * fix: assistant form uploads and better typing * refactor: consolidate logic
This commit is contained in:
parent
0870acd086
commit
65888c274a
20 changed files with 419 additions and 311 deletions
|
@ -69,9 +69,10 @@ router.delete('/', async (req, res) => {
|
|||
await processDeleteRequest({ req, files });
|
||||
|
||||
logger.debug(
|
||||
`[/files] Files deleted successfully: ${files.map(
|
||||
(f, i) => `${f.file_id}${i < files.length - 1 ? ', ' : ''}`,
|
||||
)}`,
|
||||
`[/files] Files deleted successfully: ${files
|
||||
.filter((f) => f.file_id)
|
||||
.map((f) => f.file_id)
|
||||
.join(', ')}`,
|
||||
);
|
||||
res.status(200).json({ message: 'Files deleted successfully' });
|
||||
} catch (error) {
|
||||
|
@ -220,7 +221,7 @@ router.post('/', async (req, res) => {
|
|||
try {
|
||||
await fs.unlink(file.path);
|
||||
} catch (error) {
|
||||
logger.error('[/files/images] Error deleting file after file processing:', error);
|
||||
logger.error('[/files] Error deleting file after file processing:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,9 +8,9 @@ import {
|
|||
} from 'librechat-data-provider';
|
||||
import {
|
||||
useChatContext,
|
||||
useChatFormContext,
|
||||
useAddedChatContext,
|
||||
useAssistantsMapContext,
|
||||
useChatFormContext,
|
||||
} from '~/Providers';
|
||||
import {
|
||||
useTextarea,
|
||||
|
@ -20,18 +20,17 @@ import {
|
|||
useQueryParams,
|
||||
useSubmitMessage,
|
||||
} from '~/hooks';
|
||||
import FileFormWrapper from './Files/FileFormWrapper';
|
||||
import { TextareaAutosize } from '~/components/ui';
|
||||
import { useGetFileConfig } from '~/data-provider';
|
||||
import { cn, removeFocusRings } from '~/utils';
|
||||
import TextareaHeader from './TextareaHeader';
|
||||
import PromptsCommand from './PromptsCommand';
|
||||
import AttachFile from './Files/AttachFile';
|
||||
import AudioRecorder from './AudioRecorder';
|
||||
import { mainTextareaId } from '~/common';
|
||||
import StreamAudio from './StreamAudio';
|
||||
import StopButton from './StopButton';
|
||||
import SendButton from './SendButton';
|
||||
import FileRow from './Files/FileRow';
|
||||
import Mention from './Mention';
|
||||
import store from '~/store';
|
||||
|
||||
|
@ -73,7 +72,6 @@ const ChatForm = ({ index = 0 }) => {
|
|||
conversation,
|
||||
isSubmitting,
|
||||
filesLoading,
|
||||
setFilesLoading,
|
||||
newConversation,
|
||||
handleStopGenerating,
|
||||
} = useChatContext();
|
||||
|
@ -130,6 +128,9 @@ const ChatForm = ({ index = 0 }) => {
|
|||
}
|
||||
}, [isSearching, disableInputs]);
|
||||
|
||||
const endpointSupportsFiles: boolean = supportsFiles[endpointType ?? endpoint ?? ''] ?? false;
|
||||
const isUploadDisabled: boolean = endpointFileConfig?.disabled ?? false;
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={methods.handleSubmit((data) => submitMessage(data))}
|
||||
|
@ -157,52 +158,37 @@ const ChatForm = ({ index = 0 }) => {
|
|||
<PromptsCommand index={index} textAreaRef={textAreaRef} submitPrompt={submitPrompt} />
|
||||
<div className="bg-token-main-surface-primary relative flex w-full flex-grow flex-col overflow-hidden rounded-2xl border dark:border-gray-600 dark:text-white [&:has(textarea:focus)]:border-gray-300 [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)] dark:[&:has(textarea:focus)]:border-gray-500">
|
||||
<TextareaHeader addedConvo={addedConvo} setAddedConvo={setAddedConvo} />
|
||||
<FileRow
|
||||
files={files}
|
||||
setFiles={setFiles}
|
||||
setFilesLoading={setFilesLoading}
|
||||
isRTL={isRTL}
|
||||
Wrapper={({ children }) => (
|
||||
<div className="mx-2 mt-2 flex flex-wrap gap-2 px-2.5 md:pl-0 md:pr-4">
|
||||
{children}
|
||||
</div>
|
||||
<FileFormWrapper disableInputs={disableInputs}>
|
||||
{endpoint && (
|
||||
<TextareaAutosize
|
||||
{...registerProps}
|
||||
ref={(e) => {
|
||||
ref(e);
|
||||
textAreaRef.current = e;
|
||||
}}
|
||||
disabled={disableInputs}
|
||||
onPaste={handlePaste}
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyUp={handleKeyUp}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
id={mainTextareaId}
|
||||
tabIndex={0}
|
||||
data-testid="text-input"
|
||||
style={{ height: 44, overflowY: 'auto' }}
|
||||
rows={1}
|
||||
className={cn(
|
||||
endpointSupportsFiles && !isUploadDisabled
|
||||
? 'pl-10 md:pl-[55px]'
|
||||
: 'pl-3 md:pl-4',
|
||||
'm-0 w-full resize-none border-0 bg-transparent py-[10px] placeholder-black/50 focus:ring-0 focus-visible:ring-0 dark:bg-transparent dark:placeholder-white/50 md:py-3.5 ',
|
||||
SpeechToText && !isRTL ? 'pr-20 md:pr-[85px]' : 'pr-10 md:pr-12',
|
||||
'max-h-[65vh] md:max-h-[75vh]',
|
||||
removeFocusRings,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{endpoint && (
|
||||
<TextareaAutosize
|
||||
{...registerProps}
|
||||
ref={(e) => {
|
||||
ref(e);
|
||||
textAreaRef.current = e;
|
||||
}}
|
||||
disabled={disableInputs}
|
||||
onPaste={handlePaste}
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyUp={handleKeyUp}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
id={mainTextareaId}
|
||||
tabIndex={0}
|
||||
data-testid="text-input"
|
||||
style={{ height: 44, overflowY: 'auto' }}
|
||||
rows={1}
|
||||
className={cn(
|
||||
supportsFiles[endpointType ?? endpoint ?? ''] && !endpointFileConfig?.disabled
|
||||
? ' pl-10 md:pl-[55px]'
|
||||
: 'pl-3 md:pl-4',
|
||||
'm-0 w-full resize-none border-0 bg-transparent py-[10px] placeholder-black/50 focus:ring-0 focus-visible:ring-0 dark:bg-transparent dark:placeholder-white/50 md:py-3.5 ',
|
||||
SpeechToText && !isRTL ? 'pr-20 md:pr-[85px]' : 'pr-10 md:pr-12',
|
||||
'max-h-[65vh] md:max-h-[75vh]',
|
||||
removeFocusRings,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<AttachFile
|
||||
endpoint={_endpoint ?? ''}
|
||||
endpointType={endpointType}
|
||||
isRTL={isRTL}
|
||||
disabled={disableInputs}
|
||||
/>
|
||||
</FileFormWrapper>
|
||||
{(isSubmitting || isSubmittingAdded) && (showStopButton || showStopAdded) ? (
|
||||
<StopButton
|
||||
stop={handleStopGenerating}
|
||||
|
|
|
@ -1,37 +1,20 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
EModelEndpoint,
|
||||
supportsFiles,
|
||||
fileConfig as defaultFileConfig,
|
||||
mergeFileConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import { FileUpload, TooltipAnchor } from '~/components/ui';
|
||||
import { useFileHandling, useLocalize } from '~/hooks';
|
||||
import { useGetFileConfig } from '~/data-provider';
|
||||
import { AttachmentIcon } from '~/components/svg';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const AttachFile = ({
|
||||
endpoint,
|
||||
endpointType,
|
||||
isRTL,
|
||||
disabled = false,
|
||||
disabled,
|
||||
handleFileChange,
|
||||
}: {
|
||||
endpoint: EModelEndpoint | '';
|
||||
endpointType?: EModelEndpoint;
|
||||
isRTL: boolean;
|
||||
disabled?: boolean | null;
|
||||
handleFileChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const { handleFileChange } = useFileHandling();
|
||||
const { data: fileConfig = defaultFileConfig } = useGetFileConfig({
|
||||
select: (data) => mergeFileConfig(data),
|
||||
});
|
||||
const endpointFileConfig = fileConfig.endpoints[endpoint ?? ''];
|
||||
|
||||
if (!supportsFiles[endpointType ?? endpoint ?? ''] || endpointFileConfig?.disabled) {
|
||||
return null;
|
||||
}
|
||||
const isUploadDisabled = disabled ?? false;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -45,8 +28,8 @@ const AttachFile = ({
|
|||
<FileUpload handleFileChange={handleFileChange} className="flex">
|
||||
<TooltipAnchor
|
||||
id="audio-recorder"
|
||||
disabled={isUploadDisabled}
|
||||
aria-label={localize('com_sidepanel_attach_files')}
|
||||
disabled={!!disabled}
|
||||
className="btn relative text-black focus:outline-none focus:ring-2 focus:ring-border-xheavy focus:ring-opacity-50 dark:text-white"
|
||||
style={{ padding: 0 }}
|
||||
description={localize('com_sidepanel_attach_files')}
|
||||
|
|
62
client/src/components/Chat/Input/Files/FileFormWrapper.tsx
Normal file
62
client/src/components/Chat/Input/Files/FileFormWrapper.tsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { memo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import {
|
||||
supportsFiles,
|
||||
mergeFileConfig,
|
||||
EndpointFileConfig,
|
||||
fileConfig as defaultFileConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import { useGetFileConfig } from '~/data-provider';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { useFileHandling } from '~/hooks';
|
||||
import AttachFile from './AttachFile';
|
||||
import FileRow from './FileRow';
|
||||
import store from '~/store';
|
||||
|
||||
function FileFormWrapper({ children, disableInputs } : {
|
||||
disableInputs: boolean;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const { handleFileChange, abortUpload } = useFileHandling();
|
||||
const chatDirection = useRecoilValue(store.chatDirection).toLowerCase();
|
||||
|
||||
const {
|
||||
files,
|
||||
setFiles,
|
||||
conversation,
|
||||
setFilesLoading,
|
||||
} = useChatContext();
|
||||
const { data: fileConfig = defaultFileConfig } = useGetFileConfig({
|
||||
select: (data) => mergeFileConfig(data),
|
||||
});
|
||||
|
||||
const isRTL = chatDirection === 'rtl';
|
||||
|
||||
const { endpoint: _endpoint, endpointType } = conversation ?? { endpoint: null };
|
||||
const endpointFileConfig = fileConfig.endpoints[_endpoint ?? ''] as EndpointFileConfig | undefined;
|
||||
const endpointSupportsFiles: boolean = supportsFiles[endpointType ?? _endpoint ?? ''] ?? false;
|
||||
const isUploadDisabled = (disableInputs || endpointFileConfig?.disabled) ?? false;
|
||||
|
||||
return (<>
|
||||
<FileRow
|
||||
files={files}
|
||||
setFiles={setFiles}
|
||||
abortUpload={abortUpload}
|
||||
setFilesLoading={setFilesLoading}
|
||||
isRTL={isRTL}
|
||||
Wrapper={({ children }) => (
|
||||
<div className="mx-2 mt-2 flex flex-wrap gap-2 px-2.5 md:pl-0 md:pr-4">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
{children}
|
||||
{endpointSupportsFiles && !isUploadDisabled && <AttachFile
|
||||
isRTL={isRTL}
|
||||
disabled={disableInputs}
|
||||
handleFileChange={handleFileChange}
|
||||
/>}
|
||||
</>);
|
||||
}
|
||||
|
||||
export default memo(FileFormWrapper);
|
|
@ -4,11 +4,13 @@ import type { ExtendedFile } from '~/common';
|
|||
import { useDeleteFilesMutation } from '~/data-provider';
|
||||
import { useFileDeletion } from '~/hooks/Files';
|
||||
import FileContainer from './FileContainer';
|
||||
import { logger } from '~/utils';
|
||||
import Image from './Image';
|
||||
|
||||
export default function FileRow({
|
||||
files: _files,
|
||||
setFiles,
|
||||
abortUpload,
|
||||
setFilesLoading,
|
||||
assistant_id,
|
||||
agent_id,
|
||||
|
@ -18,6 +20,7 @@ export default function FileRow({
|
|||
Wrapper,
|
||||
}: {
|
||||
files: Map<string, ExtendedFile> | undefined;
|
||||
abortUpload?: () => void;
|
||||
setFiles: React.Dispatch<React.SetStateAction<Map<string, ExtendedFile>>>;
|
||||
setFilesLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
fileFilter?: (file: ExtendedFile) => boolean;
|
||||
|
@ -33,7 +36,8 @@ export default function FileRow({
|
|||
|
||||
const { mutateAsync } = useDeleteFilesMutation({
|
||||
onMutate: async () =>
|
||||
console.log(
|
||||
logger.log(
|
||||
'agents',
|
||||
'Deleting files: agent_id, assistant_id, tool_resource',
|
||||
agent_id,
|
||||
assistant_id,
|
||||
|
@ -86,7 +90,12 @@ export default function FileRow({
|
|||
{ map: new Map(), uniqueFiles: [] as ExtendedFile[] },
|
||||
)
|
||||
.uniqueFiles.map((file: ExtendedFile, index: number) => {
|
||||
const handleDelete = () => deleteFile({ file, setFiles });
|
||||
const handleDelete = () => {
|
||||
if (abortUpload && file.progress < 1) {
|
||||
abortUpload();
|
||||
}
|
||||
deleteFile({ file, setFiles });
|
||||
};
|
||||
const isImage = file.type?.startsWith('image') ?? false;
|
||||
if (isImage) {
|
||||
return (
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
mergeFileConfig,
|
||||
fileConfig as defaultFileConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import type { EndpointFileConfig } from 'librechat-data-provider';
|
||||
import type { ExtendedFile } from '~/common';
|
||||
import FileRow from '~/components/Chat/Input/Files/FileRow';
|
||||
import { useGetFileConfig } from '~/data-provider';
|
||||
|
@ -40,9 +41,12 @@ export default function CodeFiles({
|
|||
}
|
||||
}, [_files]);
|
||||
|
||||
const endpointFileConfig = fileConfig.endpoints[EModelEndpoint.agents];
|
||||
const endpointFileConfig = fileConfig.endpoints[EModelEndpoint.agents] as
|
||||
| EndpointFileConfig
|
||||
| undefined;
|
||||
const isUploadDisabled = endpointFileConfig?.disabled ?? false;
|
||||
|
||||
if (endpointFileConfig?.disabled) {
|
||||
if (isUploadDisabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
mergeFileConfig,
|
||||
fileConfig as defaultFileConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import type { AssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { AssistantsEndpoint, EndpointFileConfig } from 'librechat-data-provider';
|
||||
import type { ExtendedFile } from '~/common';
|
||||
import FileRow from '~/components/Chat/Input/Files/FileRow';
|
||||
import { useGetFileConfig } from '~/data-provider';
|
||||
|
@ -43,9 +43,10 @@ export default function CodeFiles({
|
|||
}
|
||||
}, [_files]);
|
||||
|
||||
const endpointFileConfig = fileConfig.endpoints[endpoint];
|
||||
const endpointFileConfig = fileConfig.endpoints[endpoint] as EndpointFileConfig | undefined;
|
||||
const isUploadDisabled = endpointFileConfig?.disabled ?? false;
|
||||
|
||||
if (endpointFileConfig.disabled) {
|
||||
if (isUploadDisabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
retrievalMimeTypes,
|
||||
fileConfig as defaultFileConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import type { AssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { AssistantsEndpoint, EndpointFileConfig } from 'librechat-data-provider';
|
||||
import type { ExtendedFile } from '~/common';
|
||||
import FileRow from '~/components/Chat/Input/Files/FileRow';
|
||||
import { useGetFileConfig } from '~/data-provider';
|
||||
|
@ -53,9 +53,10 @@ export default function Knowledge({
|
|||
}
|
||||
}, [_files]);
|
||||
|
||||
const endpointFileConfig = fileConfig.endpoints[endpoint];
|
||||
const endpointFileConfig = fileConfig.endpoints[endpoint] as EndpointFileConfig | undefined;
|
||||
const isUploadDisabled = endpointFileConfig?.disabled ?? false;
|
||||
|
||||
if (endpointFileConfig?.disabled) {
|
||||
if (isUploadDisabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export * from './queries';
|
||||
export * from './mutations';
|
||||
|
|
159
client/src/data-provider/Files/mutations.ts
Normal file
159
client/src/data-provider/Files/mutations.ts
Normal file
|
@ -0,0 +1,159 @@
|
|||
import { EToolResources } from 'librechat-data-provider';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { dataService, MutationKeys, QueryKeys, defaultOrderQuery } from 'librechat-data-provider';
|
||||
import type * as t from 'librechat-data-provider';
|
||||
import type { UseMutationResult } from '@tanstack/react-query';
|
||||
|
||||
export type TGenTitleMutation = UseMutationResult<
|
||||
t.TGenTitleResponse,
|
||||
unknown,
|
||||
t.TGenTitleRequest,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export const useUploadFileMutation = (
|
||||
_options?: t.UploadMutationOptions,
|
||||
signal?: AbortSignal | null,
|
||||
): UseMutationResult<
|
||||
t.TFileUpload, // response data
|
||||
unknown, // error
|
||||
FormData, // request
|
||||
unknown // context
|
||||
> => {
|
||||
const queryClient = useQueryClient();
|
||||
const { onSuccess, ...options } = _options || {};
|
||||
return useMutation([MutationKeys.fileUpload], {
|
||||
mutationFn: (body: FormData) => {
|
||||
const width = body.get('width') ?? '';
|
||||
const height = body.get('height') ?? '';
|
||||
const version = body.get('version') ?? '';
|
||||
|
||||
if (width !== '' && height !== '' && (version !== '' || version.toString() !== '2')) {
|
||||
return dataService.uploadImage(body, signal);
|
||||
}
|
||||
|
||||
return dataService.uploadFile(body, signal);
|
||||
},
|
||||
...options,
|
||||
onSuccess: (data, formData, context) => {
|
||||
queryClient.setQueryData<t.TFile[] | undefined>([QueryKeys.files], (_files) => [
|
||||
data,
|
||||
...(_files ?? []),
|
||||
]);
|
||||
|
||||
const endpoint = formData.get('endpoint');
|
||||
const message_file = formData.get('message_file');
|
||||
const agent_id = (formData.get('agent_id') as string | undefined) ?? '';
|
||||
const assistant_id = (formData.get('assistant_id') as string | undefined) ?? '';
|
||||
const tool_resource = (formData.get('tool_resource') as string | undefined) ?? '';
|
||||
|
||||
if (message_file === 'true') {
|
||||
onSuccess?.(data, formData, context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (agent_id && tool_resource) {
|
||||
queryClient.setQueryData<t.Agent>([QueryKeys.agent, agent_id], (agent) => {
|
||||
if (!agent) {
|
||||
return agent;
|
||||
}
|
||||
|
||||
const update = {};
|
||||
const prevResources = agent.tool_resources ?? {};
|
||||
const prevResource: t.ExecuteCodeResource | t.AgentFileSearchResource = agent
|
||||
.tool_resources?.[tool_resource] ?? {
|
||||
file_ids: [],
|
||||
};
|
||||
if (!prevResource.file_ids) {
|
||||
prevResource.file_ids = [];
|
||||
}
|
||||
prevResource.file_ids.push(data.file_id);
|
||||
update['tool_resources'] = {
|
||||
...prevResources,
|
||||
[tool_resource]: prevResource,
|
||||
};
|
||||
return {
|
||||
...agent,
|
||||
...update,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (!assistant_id) {
|
||||
onSuccess?.(data, formData, context);
|
||||
return;
|
||||
}
|
||||
|
||||
queryClient.setQueryData<t.AssistantListResponse>(
|
||||
[QueryKeys.assistants, endpoint, defaultOrderQuery],
|
||||
(prev) => {
|
||||
if (!prev) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
data: prev.data.map((assistant) => {
|
||||
if (assistant.id !== assistant_id) {
|
||||
return assistant;
|
||||
}
|
||||
|
||||
const update = {};
|
||||
if (!tool_resource) {
|
||||
update['file_ids'] = [...(assistant.file_ids ?? []), data.file_id];
|
||||
}
|
||||
if (tool_resource === EToolResources.code_interpreter) {
|
||||
const prevResources = assistant.tool_resources ?? {};
|
||||
const prevResource = assistant.tool_resources?.[tool_resource] ?? {
|
||||
file_ids: [],
|
||||
};
|
||||
if (!prevResource.file_ids) {
|
||||
prevResource.file_ids = [];
|
||||
}
|
||||
prevResource.file_ids.push(data.file_id);
|
||||
update['tool_resources'] = {
|
||||
...prevResources,
|
||||
[tool_resource]: prevResource,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...assistant,
|
||||
...update,
|
||||
};
|
||||
}),
|
||||
};
|
||||
},
|
||||
);
|
||||
onSuccess?.(data, formData, context);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteFilesMutation = (
|
||||
_options?: t.DeleteMutationOptions,
|
||||
): UseMutationResult<
|
||||
t.DeleteFilesResponse, // response data
|
||||
unknown, // error
|
||||
t.DeleteFilesBody, // request
|
||||
unknown // context
|
||||
> => {
|
||||
const queryClient = useQueryClient();
|
||||
const { onSuccess, ...options } = _options || {};
|
||||
return useMutation([MutationKeys.fileDelete], {
|
||||
mutationFn: (body: t.DeleteFilesBody) => dataService.deleteFiles(body),
|
||||
...options,
|
||||
onSuccess: (data, ...args) => {
|
||||
queryClient.setQueryData<t.TFile[] | undefined>([QueryKeys.files], (cachefiles) => {
|
||||
const { files: filesDeleted } = args[0];
|
||||
|
||||
const fileMap = filesDeleted.reduce((acc, file) => {
|
||||
acc.set(file.file_id, file);
|
||||
return acc;
|
||||
}, new Map<string, t.BatchFile>());
|
||||
|
||||
return (cachefiles ?? []).filter((file) => !fileMap.has(file.file_id));
|
||||
});
|
||||
onSuccess?.(data, ...args);
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,6 +1,5 @@
|
|||
import {
|
||||
Constants,
|
||||
EToolResources,
|
||||
LocalStorageKeys,
|
||||
InfiniteCollections,
|
||||
defaultAssistantsVersion,
|
||||
|
@ -622,151 +621,6 @@ export const useUploadConversationsMutation = (
|
|||
});
|
||||
};
|
||||
|
||||
export const useUploadFileMutation = (
|
||||
_options?: t.UploadMutationOptions,
|
||||
): UseMutationResult<
|
||||
t.TFileUpload, // response data
|
||||
unknown, // error
|
||||
FormData, // request
|
||||
unknown // context
|
||||
> => {
|
||||
const queryClient = useQueryClient();
|
||||
const { onSuccess, ...options } = _options || {};
|
||||
return useMutation([MutationKeys.fileUpload], {
|
||||
mutationFn: (body: FormData) => {
|
||||
const width = body.get('width');
|
||||
const height = body.get('height');
|
||||
const version = body.get('version') as number | string;
|
||||
if (height && width && (!version || version != 2)) {
|
||||
return dataService.uploadImage(body);
|
||||
}
|
||||
|
||||
return dataService.uploadFile(body);
|
||||
},
|
||||
...options,
|
||||
onSuccess: (data, formData, context) => {
|
||||
queryClient.setQueryData<t.TFile[] | undefined>([QueryKeys.files], (_files) => [
|
||||
data,
|
||||
...(_files ?? []),
|
||||
]);
|
||||
|
||||
const endpoint = formData.get('endpoint');
|
||||
const message_file = formData.get('message_file');
|
||||
const agent_id = (formData.get('agent_id') as string | undefined) ?? '';
|
||||
const assistant_id = (formData.get('assistant_id') as string | undefined) ?? '';
|
||||
const tool_resource = (formData.get('tool_resource') as string | undefined) ?? '';
|
||||
|
||||
if (message_file === 'true') {
|
||||
onSuccess?.(data, formData, context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (agent_id && tool_resource) {
|
||||
queryClient.setQueryData<t.Agent>([QueryKeys.agent, agent_id], (agent) => {
|
||||
if (!agent) {
|
||||
return agent;
|
||||
}
|
||||
|
||||
const update = {};
|
||||
const prevResources = agent.tool_resources ?? {};
|
||||
const prevResource: t.ExecuteCodeResource | t.AgentFileSearchResource = agent
|
||||
.tool_resources?.[tool_resource] ?? {
|
||||
file_ids: [],
|
||||
};
|
||||
if (!prevResource.file_ids) {
|
||||
prevResource.file_ids = [];
|
||||
}
|
||||
prevResource.file_ids.push(data.file_id);
|
||||
update['tool_resources'] = {
|
||||
...prevResources,
|
||||
[tool_resource]: prevResource,
|
||||
};
|
||||
return {
|
||||
...agent,
|
||||
...update,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (!assistant_id) {
|
||||
onSuccess?.(data, formData, context);
|
||||
return;
|
||||
}
|
||||
|
||||
queryClient.setQueryData<t.AssistantListResponse>(
|
||||
[QueryKeys.assistants, endpoint, defaultOrderQuery],
|
||||
(prev) => {
|
||||
if (!prev) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
data: prev.data.map((assistant) => {
|
||||
if (assistant.id !== assistant_id) {
|
||||
return assistant;
|
||||
}
|
||||
|
||||
const update = {};
|
||||
if (!tool_resource) {
|
||||
update['file_ids'] = [...(assistant.file_ids ?? []), data.file_id];
|
||||
}
|
||||
if (tool_resource === EToolResources.code_interpreter) {
|
||||
const prevResources = assistant.tool_resources ?? {};
|
||||
const prevResource = assistant.tool_resources?.[tool_resource] ?? {
|
||||
file_ids: [],
|
||||
};
|
||||
if (!prevResource.file_ids) {
|
||||
prevResource.file_ids = [];
|
||||
}
|
||||
prevResource.file_ids.push(data.file_id);
|
||||
update['tool_resources'] = {
|
||||
...prevResources,
|
||||
[tool_resource]: prevResource,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...assistant,
|
||||
...update,
|
||||
};
|
||||
}),
|
||||
};
|
||||
},
|
||||
);
|
||||
onSuccess?.(data, formData, context);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteFilesMutation = (
|
||||
_options?: t.DeleteMutationOptions,
|
||||
): UseMutationResult<
|
||||
t.DeleteFilesResponse, // response data
|
||||
unknown, // error
|
||||
t.DeleteFilesBody, // request
|
||||
unknown // context
|
||||
> => {
|
||||
const queryClient = useQueryClient();
|
||||
const { onSuccess, ...options } = _options || {};
|
||||
return useMutation([MutationKeys.fileDelete], {
|
||||
mutationFn: (body: t.DeleteFilesBody) => dataService.deleteFiles(body),
|
||||
...options,
|
||||
onSuccess: (data, ...args) => {
|
||||
queryClient.setQueryData<t.TFile[] | undefined>([QueryKeys.files], (cachefiles) => {
|
||||
const { files: filesDeleted } = args[0];
|
||||
|
||||
const fileMap = filesDeleted.reduce((acc, file) => {
|
||||
acc.set(file.file_id, file);
|
||||
return acc;
|
||||
}, new Map<string, t.BatchFile>());
|
||||
|
||||
return (cachefiles ?? []).filter((file) => !fileMap.has(file.file_id));
|
||||
});
|
||||
onSuccess?.(data, ...args);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdatePresetMutation = (
|
||||
options?: t.UpdatePresetOptions,
|
||||
): UseMutationResult<
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { v4 } from 'uuid';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import {
|
||||
megabyte,
|
||||
QueryKeys,
|
||||
|
@ -18,7 +18,9 @@ import { useUploadFileMutation, useGetFileConfig } from '~/data-provider';
|
|||
import { useDelayedUploadToast } from './useDelayedUploadToast';
|
||||
import { useToastContext } from '~/Providers/ToastContext';
|
||||
import { useChatContext } from '~/Providers/ChatContext';
|
||||
import useLocalize from '~/hooks/useLocalize';
|
||||
import useUpdateFiles from './useUpdateFiles';
|
||||
import { logger } from '~/utils';
|
||||
|
||||
const { checkType } = defaultFileConfig;
|
||||
|
||||
|
@ -30,9 +32,11 @@ type UseFileHandling = {
|
|||
};
|
||||
|
||||
const useFileHandling = (params?: UseFileHandling) => {
|
||||
const localize = useLocalize();
|
||||
const queryClient = useQueryClient();
|
||||
const { showToast } = useToastContext();
|
||||
const [errors, setErrors] = useState<string[]>([]);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
const { startUploadTimer, clearUploadTimer } = useDelayedUploadToast();
|
||||
const { files, setFiles, setFilesLoading, conversation } = useChatContext();
|
||||
const setError = (error: string) => setErrors((prevErrors) => [...prevErrors, error]);
|
||||
|
@ -55,7 +59,7 @@ const useFileHandling = (params?: UseFileHandling) => {
|
|||
const displayToast = useCallback(() => {
|
||||
if (errors.length > 1) {
|
||||
const errorList = Array.from(new Set(errors))
|
||||
.map((e, i) => `${i > 0 ? '• ' : ''}${e}\n`)
|
||||
.map((e, i) => `${i > 0 ? '• ' : ''}${localize(e) || e}\n`)
|
||||
.join('');
|
||||
showToast({
|
||||
message: errorList,
|
||||
|
@ -63,15 +67,16 @@ const useFileHandling = (params?: UseFileHandling) => {
|
|||
duration: 5000,
|
||||
});
|
||||
} else if (errors.length === 1) {
|
||||
const message = localize(errors[0]) || errors[0];
|
||||
showToast({
|
||||
message: errors[0],
|
||||
message,
|
||||
status: 'error',
|
||||
duration: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
setErrors([]);
|
||||
}, [errors, showToast]);
|
||||
}, [errors, showToast, localize]);
|
||||
|
||||
const debouncedDisplayToast = debounce(displayToast, 250);
|
||||
|
||||
|
@ -83,53 +88,58 @@ const useFileHandling = (params?: UseFileHandling) => {
|
|||
return () => debouncedDisplayToast.cancel();
|
||||
}, [errors, debouncedDisplayToast]);
|
||||
|
||||
const uploadFile = useUploadFileMutation({
|
||||
onSuccess: (data) => {
|
||||
clearUploadTimer(data.temp_file_id);
|
||||
console.log('upload success', data);
|
||||
if (agent_id) {
|
||||
queryClient.refetchQueries([QueryKeys.agent, agent_id]);
|
||||
return;
|
||||
}
|
||||
updateFileById(
|
||||
data.temp_file_id,
|
||||
{
|
||||
progress: 0.9,
|
||||
filepath: data.filepath,
|
||||
},
|
||||
assistant_id ? true : false,
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
const uploadFile = useUploadFileMutation(
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
clearUploadTimer(data.temp_file_id);
|
||||
console.log('upload success', data);
|
||||
if (agent_id) {
|
||||
queryClient.refetchQueries([QueryKeys.agent, agent_id]);
|
||||
return;
|
||||
}
|
||||
updateFileById(
|
||||
data.temp_file_id,
|
||||
{
|
||||
progress: 1,
|
||||
file_id: data.file_id,
|
||||
temp_file_id: data.temp_file_id,
|
||||
progress: 0.9,
|
||||
filepath: data.filepath,
|
||||
type: data.type,
|
||||
height: data.height,
|
||||
width: data.width,
|
||||
filename: data.filename,
|
||||
source: data.source,
|
||||
embedded: data.embedded,
|
||||
},
|
||||
assistant_id ? true : false,
|
||||
);
|
||||
}, 300);
|
||||
|
||||
setTimeout(() => {
|
||||
updateFileById(
|
||||
data.temp_file_id,
|
||||
{
|
||||
progress: 1,
|
||||
file_id: data.file_id,
|
||||
temp_file_id: data.temp_file_id,
|
||||
filepath: data.filepath,
|
||||
type: data.type,
|
||||
height: data.height,
|
||||
width: data.width,
|
||||
filename: data.filename,
|
||||
source: data.source,
|
||||
embedded: data.embedded,
|
||||
},
|
||||
assistant_id ? true : false,
|
||||
);
|
||||
}, 300);
|
||||
},
|
||||
onError: (_error, body) => {
|
||||
const error = _error as TError | undefined;
|
||||
console.log('upload error', error);
|
||||
const file_id = body.get('file_id');
|
||||
clearUploadTimer(file_id as string);
|
||||
deleteFileById(file_id as string);
|
||||
const errorMessage =
|
||||
error?.code === 'ERR_CANCELED'
|
||||
? 'com_error_files_upload_canceled'
|
||||
: error?.response?.data?.message ?? 'com_error_files_upload';
|
||||
setError(errorMessage);
|
||||
},
|
||||
},
|
||||
onError: (error, body) => {
|
||||
console.log('upload error', error);
|
||||
const file_id = body.get('file_id');
|
||||
clearUploadTimer(file_id as string);
|
||||
deleteFileById(file_id as string);
|
||||
setError(
|
||||
(error as TError | undefined)?.response?.data?.message ??
|
||||
'An error occurred while uploading the file.',
|
||||
);
|
||||
},
|
||||
});
|
||||
abortControllerRef.current?.signal,
|
||||
);
|
||||
|
||||
const startUpload = async (extendedFile: ExtendedFile) => {
|
||||
const filename = extendedFile.file?.name ?? 'File';
|
||||
|
@ -148,33 +158,47 @@ const useFileHandling = (params?: UseFileHandling) => {
|
|||
formData.append('height', height.toString());
|
||||
}
|
||||
|
||||
const metadata = params?.additionalMetadata ?? {};
|
||||
if (params?.additionalMetadata) {
|
||||
for (const [key, value = ''] of Object.entries(params.additionalMetadata)) {
|
||||
for (const [key, value = ''] of Object.entries(metadata)) {
|
||||
if (value) {
|
||||
formData.append(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const convoAssistantId = conversation?.assistant_id ?? '';
|
||||
const convoModel = conversation?.model ?? '';
|
||||
if (isAssistantsEndpoint(endpoint) && !formData.get('assistant_id') && convoAssistantId) {
|
||||
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
formData.append('version', version);
|
||||
formData.append('assistant_id', convoAssistantId);
|
||||
formData.append('model', convoModel);
|
||||
formData.append('message_file', 'true');
|
||||
formData.append('endpoint', endpoint);
|
||||
|
||||
if (!isAssistantsEndpoint(endpoint)) {
|
||||
uploadFile.mutate(formData);
|
||||
return;
|
||||
}
|
||||
if (isAssistantsEndpoint(endpoint) && !formData.get('version')) {
|
||||
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
formData.append('version', version);
|
||||
formData.append('model', conversation?.model ?? '');
|
||||
|
||||
const convoModel = conversation?.model ?? '';
|
||||
const convoAssistantId = conversation?.assistant_id ?? '';
|
||||
|
||||
if (!assistant_id) {
|
||||
formData.append('message_file', 'true');
|
||||
}
|
||||
|
||||
formData.append('endpoint', endpoint);
|
||||
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
|
||||
if (!assistant_id && convoAssistantId) {
|
||||
formData.append('version', version);
|
||||
formData.append('model', convoModel);
|
||||
formData.append('assistant_id', convoAssistantId);
|
||||
}
|
||||
|
||||
const formVersion = (formData.get('version') ?? '') as string;
|
||||
if (!formVersion) {
|
||||
formData.append('version', version);
|
||||
}
|
||||
|
||||
const formModel = (formData.get('model') ?? '') as string;
|
||||
if (!formModel) {
|
||||
formData.append('model', convoModel);
|
||||
}
|
||||
|
||||
uploadFile.mutate(formData);
|
||||
};
|
||||
|
@ -183,7 +207,7 @@ const useFileHandling = (params?: UseFileHandling) => {
|
|||
const existingFiles = Array.from(files.values());
|
||||
const incomingTotalSize = fileList.reduce((total, file) => total + file.size, 0);
|
||||
if (incomingTotalSize === 0) {
|
||||
setError('Empty files are not allowed.');
|
||||
setError('com_error_files_empty');
|
||||
return false;
|
||||
}
|
||||
const currentTotalSize = existingFiles.reduce((total, file) => total + file.size, 0);
|
||||
|
@ -248,7 +272,7 @@ const useFileHandling = (params?: UseFileHandling) => {
|
|||
const uniqueFilesSet = new Set(combinedFilesInfo);
|
||||
|
||||
if (uniqueFilesSet.size !== combinedFilesInfo.length) {
|
||||
setError('Duplicate file detected.');
|
||||
setError('com_error_files_dupe');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -273,6 +297,7 @@ const useFileHandling = (params?: UseFileHandling) => {
|
|||
};
|
||||
|
||||
const handleFiles = async (_files: FileList | File[]) => {
|
||||
abortControllerRef.current = new AbortController();
|
||||
const fileList = Array.from(_files);
|
||||
/* Validate files */
|
||||
let filesAreValid: boolean;
|
||||
|
@ -280,7 +305,7 @@ const useFileHandling = (params?: UseFileHandling) => {
|
|||
filesAreValid = validateFiles(fileList);
|
||||
} catch (error) {
|
||||
console.error('file validation error', error);
|
||||
setError('An error occurred while validating the file.');
|
||||
setError('com_error_files_validation');
|
||||
return;
|
||||
}
|
||||
if (!filesAreValid) {
|
||||
|
@ -313,7 +338,7 @@ const useFileHandling = (params?: UseFileHandling) => {
|
|||
} catch (error) {
|
||||
deleteFileById(file_id);
|
||||
console.log('file handling error', error);
|
||||
setError('An error occurred while processing the file.');
|
||||
setError('com_error_files_process');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -328,11 +353,20 @@ const useFileHandling = (params?: UseFileHandling) => {
|
|||
}
|
||||
};
|
||||
|
||||
const abortUpload = () => {
|
||||
if (abortControllerRef.current) {
|
||||
logger.log('files', 'Aborting upload');
|
||||
abortControllerRef.current.abort('User aborted upload');
|
||||
abortControllerRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handleFileChange,
|
||||
handleFiles,
|
||||
files,
|
||||
abortUpload,
|
||||
setFiles,
|
||||
files,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ export const getTranslations = (langCode: string): Language => {
|
|||
|
||||
// input: language code in string & phrase key in string
|
||||
// returns an corresponding phrase value in string
|
||||
export const localize = (langCode: string, phraseKey: string, ...values: string[]) => {
|
||||
export const localize = (langCode: string, phraseKey: string, ...values: string[]): string => {
|
||||
const lang = getTranslations(langCode);
|
||||
const phrase = lang[phraseKey] || English[phraseKey] || '';
|
||||
|
||||
|
|
|
@ -33,6 +33,13 @@ export default {
|
|||
'Provided key for {0} expired at {1}. Please provide a new key and try again.',
|
||||
com_error_input_length:
|
||||
'The latest message token count is too long, exceeding the token limit ({0} respectively). Please shorten your message, adjust the max context size from the conversation parameters, or fork the conversation to continue.',
|
||||
com_error_files_empty: 'Empty files are not allowed.',
|
||||
com_error_files_dupe: 'Duplicate file detected.',
|
||||
com_error_files_validation: 'An error occurred while validating the file.',
|
||||
com_error_files_process: 'An error occurred while processing the file.',
|
||||
com_error_files_upload: 'An error occurred while uploading the file.',
|
||||
com_error_files_upload_canceled:
|
||||
'The file upload request was canceled. Note: the file upload may still be processing and will need to be manually deleted.',
|
||||
com_files_no_results: 'No results.',
|
||||
com_files_filter: 'Filter files...',
|
||||
com_generated_files: 'Generated files:',
|
||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -36522,7 +36522,7 @@
|
|||
},
|
||||
"packages/data-provider": {
|
||||
"name": "librechat-data-provider",
|
||||
"version": "0.7.427",
|
||||
"version": "0.7.428",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "librechat-data-provider",
|
||||
"version": "0.7.427",
|
||||
"version": "0.7.428",
|
||||
"description": "data services for librechat apps",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.es.js",
|
||||
|
|
|
@ -44,8 +44,8 @@ export function getSharedMessages(shareId: string): Promise<t.TSharedMessagesRes
|
|||
export const listSharedLinks = (
|
||||
params?: q.SharedLinkListParams,
|
||||
): Promise<q.SharedLinksResponse> => {
|
||||
const pageNumber = params?.pageNumber || '1'; // Default to page 1 if not provided
|
||||
const isPublic = params?.isPublic || true; // Default to true if not provided
|
||||
const pageNumber = (params?.pageNumber ?? '1') || '1'; // Default to page 1 if not provided
|
||||
const isPublic = params?.isPublic ?? true; // Default to true if not provided
|
||||
return request.get(endpoints.getSharedLinks(pageNumber, isPublic));
|
||||
};
|
||||
|
||||
|
@ -314,12 +314,14 @@ export const getFileConfig = (): Promise<f.FileConfig> => {
|
|||
return request.get(`${endpoints.files()}/config`);
|
||||
};
|
||||
|
||||
export const uploadImage = (data: FormData): Promise<f.TFileUpload> => {
|
||||
return request.postMultiPart(endpoints.images(), data);
|
||||
export const uploadImage = (data: FormData, signal?: AbortSignal | null): Promise<f.TFileUpload> => {
|
||||
const requestConfig = signal ? { signal } : undefined;
|
||||
return request.postMultiPart(endpoints.images(), data, requestConfig);
|
||||
};
|
||||
|
||||
export const uploadFile = (data: FormData): Promise<f.TFileUpload> => {
|
||||
return request.postMultiPart(endpoints.files(), data);
|
||||
export const uploadFile = (data: FormData, signal?: AbortSignal | null): Promise<f.TFileUpload> => {
|
||||
const requestConfig = signal ? { signal } : undefined;
|
||||
return request.postMultiPart(endpoints.files(), data, requestConfig);
|
||||
};
|
||||
|
||||
/* actions */
|
||||
|
@ -538,8 +540,8 @@ export const listConversations = (
|
|||
params?: q.ConversationListParams,
|
||||
): Promise<q.ConversationListResponse> => {
|
||||
// Assuming params has a pageNumber property
|
||||
const pageNumber = params?.pageNumber || '1'; // Default to page 1 if not provided
|
||||
const isArchived = params?.isArchived || false; // Default to false if not provided
|
||||
const pageNumber = (params?.pageNumber ?? '1') || '1'; // Default to page 1 if not provided
|
||||
const isArchived = params?.isArchived ?? false; // Default to false if not provided
|
||||
const tags = params?.tags || []; // Default to an empty array if not provided
|
||||
return request.get(endpoints.conversations(pageNumber, isArchived, tags));
|
||||
};
|
||||
|
@ -547,8 +549,8 @@ export const listConversations = (
|
|||
export const listConversationsByQuery = (
|
||||
params?: q.ConversationListParams & { searchQuery?: string },
|
||||
): Promise<q.ConversationListResponse> => {
|
||||
const pageNumber = params?.pageNumber || '1'; // Default to page 1 if not provided
|
||||
const searchQuery = params?.searchQuery || ''; // If no search query is provided, default to an empty string
|
||||
const pageNumber = (params?.pageNumber ?? '1') || '1'; // Default to page 1 if not provided
|
||||
const searchQuery = params?.searchQuery ?? ''; // If no search query is provided, default to an empty string
|
||||
// Update the endpoint to handle a search query
|
||||
if (searchQuery !== '') {
|
||||
return request.get(endpoints.search(searchQuery, pageNumber));
|
||||
|
|
|
@ -80,6 +80,9 @@ axios.interceptors.response.use(
|
|||
(response) => response,
|
||||
async (error) => {
|
||||
const originalRequest = error.config;
|
||||
if (!error.response) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (error.response.status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
|
|
|
@ -30,8 +30,9 @@ export enum EModelEndpoint {
|
|||
|
||||
export const paramEndpoints = new Set<EModelEndpoint | string>([
|
||||
EModelEndpoint.agents,
|
||||
EModelEndpoint.bedrock,
|
||||
EModelEndpoint.openAI,
|
||||
EModelEndpoint.bedrock,
|
||||
EModelEndpoint.azureOpenAI,
|
||||
EModelEndpoint.anthropic,
|
||||
EModelEndpoint.custom,
|
||||
]);
|
||||
|
|
|
@ -93,7 +93,7 @@ export type TCategory = {
|
|||
|
||||
export type TError = {
|
||||
message: string;
|
||||
code?: number;
|
||||
code?: number | string;
|
||||
response?: {
|
||||
data?: {
|
||||
message?: string;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue