mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-29 13:46:18 +01:00
🤖 Assistants V2 Support: Part 2
🎹 fix: Autocompletion Chrome Bug on Action API Key Input
chore: remove `useOriginNavigate`
chore: set correct OpenAI Storage Source
fix: azure file deletions, instantiate clients by source for deletion
update code interpret files info
feat: deleteResourceFileId
chore: increase poll interval as azure easily rate limits
fix: openai file deletions, TODO: evaluate rejected deletion settled promises to determine which to delete from db records
file source icons
update table file filters
chore: file search info and versioning
fix: retrieval update with necessary tool_resources if specified
fix(useMentions): add optional chaining in case listMap value is undefined
fix: force assistant avatar roundedness
fix: azure assistants, check correct flag
chore: bump data-provider
This commit is contained in:
parent
2bdbff5141
commit
bc46ccdcad
44 changed files with 420 additions and 174 deletions
|
|
@ -2,6 +2,7 @@ import type { TFile } from 'librechat-data-provider';
|
|||
import type { ExtendedFile } from '~/common';
|
||||
import FileIcon from '~/components/svg/Files/FileIcon';
|
||||
import ProgressCircle from './ProgressCircle';
|
||||
import SourceIcon from './SourceIcon';
|
||||
import { useProgress } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
|
@ -21,7 +22,6 @@ const FilePreview = ({
|
|||
const radius = 55; // Radius of the SVG circle
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
const progress = useProgress(file?.['progress'] ?? 1, 0.001, (file as ExtendedFile)?.size ?? 1);
|
||||
console.log(progress);
|
||||
|
||||
// Calculate the offset based on the loading progress
|
||||
const offset = circumference - progress * circumference;
|
||||
|
|
@ -32,6 +32,7 @@ const FilePreview = ({
|
|||
return (
|
||||
<div className={cn('h-10 w-10 shrink-0 overflow-hidden rounded-md', className)}>
|
||||
<FileIcon file={file} fileType={fileType} />
|
||||
<SourceIcon source={file?.source} />
|
||||
{progress < 1 && (
|
||||
<ProgressCircle
|
||||
circumference={circumference}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useEffect } from 'react';
|
||||
import { EToolResources } from 'librechat-data-provider';
|
||||
import type { ExtendedFile } from '~/common';
|
||||
import { useDeleteFilesMutation } from '~/data-provider';
|
||||
import { useFileDeletion } from '~/hooks/Files';
|
||||
|
|
@ -10,6 +11,7 @@ export default function FileRow({
|
|||
setFiles,
|
||||
setFilesLoading,
|
||||
assistant_id,
|
||||
tool_resource,
|
||||
fileFilter,
|
||||
Wrapper,
|
||||
}: {
|
||||
|
|
@ -18,6 +20,7 @@ export default function FileRow({
|
|||
setFilesLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
fileFilter?: (file: ExtendedFile) => boolean;
|
||||
assistant_id?: string;
|
||||
tool_resource?: EToolResources;
|
||||
Wrapper?: React.FC<{ children: React.ReactNode }>;
|
||||
}) {
|
||||
const files = Array.from(_files.values()).filter((file) =>
|
||||
|
|
@ -25,7 +28,8 @@ export default function FileRow({
|
|||
);
|
||||
|
||||
const { mutateAsync } = useDeleteFilesMutation({
|
||||
onMutate: async () => console.log('Deleting files: assistant_id', assistant_id),
|
||||
onMutate: async () =>
|
||||
console.log('Deleting files: assistant_id, tool_resource', assistant_id, tool_resource),
|
||||
onSuccess: () => {
|
||||
console.log('Files deleted');
|
||||
},
|
||||
|
|
@ -34,7 +38,7 @@ export default function FileRow({
|
|||
},
|
||||
});
|
||||
|
||||
const { deleteFile } = useFileDeletion({ mutateAsync, assistant_id });
|
||||
const { deleteFile } = useFileDeletion({ mutateAsync, assistant_id, tool_resource });
|
||||
|
||||
useEffect(() => {
|
||||
if (!files) {
|
||||
|
|
@ -82,6 +86,7 @@ export default function FileRow({
|
|||
url={file.preview}
|
||||
onDelete={handleDelete}
|
||||
progress={file.progress}
|
||||
source={file.source}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,16 +12,9 @@ export default function Files({ open, onOpenChange }) {
|
|||
const { data: files = [] } = useGetFiles<TFile[]>({
|
||||
select: (files) =>
|
||||
files.map((file) => {
|
||||
if (file.source === FileSources.local || file.source === FileSources.openai) {
|
||||
file.context = file.context ?? FileContext.unknown;
|
||||
return file;
|
||||
} else {
|
||||
return {
|
||||
...file,
|
||||
context: file.context ?? FileContext.unknown,
|
||||
source: FileSources.local,
|
||||
};
|
||||
}
|
||||
file.context = file.context ?? FileContext.unknown;
|
||||
file.filterSource = file.source === FileSources.firebase ? FileSources.local : file.source;
|
||||
return file;
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { FileSources } from 'librechat-data-provider';
|
||||
import ImagePreview from './ImagePreview';
|
||||
import RemoveFile from './RemoveFile';
|
||||
|
||||
|
|
@ -6,16 +7,18 @@ const Image = ({
|
|||
url,
|
||||
onDelete,
|
||||
progress = 1,
|
||||
source = FileSources.local,
|
||||
}: {
|
||||
imageBase64?: string;
|
||||
url?: string;
|
||||
onDelete: () => void;
|
||||
progress: number; // between 0 and 1
|
||||
source?: FileSources;
|
||||
}) => {
|
||||
return (
|
||||
<div className="group relative inline-block text-sm text-black/70 dark:text-white/90">
|
||||
<div className="relative overflow-hidden rounded-xl border border-gray-200 dark:border-gray-600">
|
||||
<ImagePreview imageBase64={imageBase64} url={url} progress={progress} />
|
||||
<ImagePreview source={source} imageBase64={imageBase64} url={url} progress={progress} />
|
||||
</div>
|
||||
<RemoveFile onRemove={onDelete} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { FileSources } from 'librechat-data-provider';
|
||||
import ProgressCircle from './ProgressCircle';
|
||||
import SourceIcon from './SourceIcon';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
type styleProps = {
|
||||
|
|
@ -13,11 +15,13 @@ const ImagePreview = ({
|
|||
url,
|
||||
progress = 1,
|
||||
className = '',
|
||||
source,
|
||||
}: {
|
||||
imageBase64?: string;
|
||||
url?: string;
|
||||
progress?: number; // between 0 and 1
|
||||
className?: string;
|
||||
source?: FileSources;
|
||||
}) => {
|
||||
let style: styleProps = {
|
||||
backgroundSize: 'cover',
|
||||
|
|
@ -65,6 +69,7 @@ const ImagePreview = ({
|
|||
circleCSSProperties={circleCSSProperties}
|
||||
/>
|
||||
)}
|
||||
<SourceIcon source={source} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
45
client/src/components/Chat/Input/Files/SourceIcon.tsx
Normal file
45
client/src/components/Chat/Input/Files/SourceIcon.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { EModelEndpoint, FileSources } from 'librechat-data-provider';
|
||||
import { MinimalIcon } from '~/components/Endpoints';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const sourceToEndpoint = {
|
||||
[FileSources.openai]: EModelEndpoint.openAI,
|
||||
[FileSources.azure]: EModelEndpoint.azureOpenAI,
|
||||
};
|
||||
const sourceToClassname = {
|
||||
[FileSources.openai]: 'bg-black/65',
|
||||
[FileSources.azure]: 'azure-bg-color opacity-85',
|
||||
};
|
||||
|
||||
const defaultClassName =
|
||||
'absolute right-0 bottom-0 rounded-full p-[0.15rem] text-gray-600 transition-colors';
|
||||
|
||||
export default function SourceIcon({
|
||||
source,
|
||||
className = defaultClassName,
|
||||
}: {
|
||||
source?: FileSources;
|
||||
className?: string;
|
||||
}) {
|
||||
if (source === FileSources.local || source === FileSources.firebase) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const endpoint = sourceToEndpoint[source ?? ''];
|
||||
|
||||
if (!endpoint) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<button type="button" className={cn(className, sourceToClassname[source ?? ''] ?? '')}>
|
||||
<span className="flex items-center justify-center">
|
||||
<MinimalIcon
|
||||
endpoint={endpoint}
|
||||
size={14}
|
||||
isCreatedByUser={false}
|
||||
iconClassName="h-3 w-3"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import ImagePreview from '~/components/Chat/Input/Files/ImagePreview';
|
|||
import FilePreview from '~/components/Chat/Input/Files/FilePreview';
|
||||
import { SortFilterHeader } from './SortFilterHeader';
|
||||
import { OpenAIMinimalIcon } from '~/components/svg';
|
||||
import { AzureMinimalIcon } from '~/components/svg';
|
||||
import { Button, Checkbox } from '~/components/ui';
|
||||
import { formatDate, getFileType } from '~/utils';
|
||||
import useLocalize from '~/hooks/useLocalize';
|
||||
|
|
@ -71,10 +72,11 @@ export const columns: ColumnDef<TFile>[] = [
|
|||
const file = row.original;
|
||||
if (file.type?.startsWith('image')) {
|
||||
return (
|
||||
<div className="flex gap-2 ">
|
||||
<div className="flex gap-2">
|
||||
<ImagePreview
|
||||
url={file.filepath}
|
||||
className="h-10 w-10 shrink-0 overflow-hidden rounded-md"
|
||||
className="relative h-10 w-10 shrink-0 overflow-hidden rounded-md"
|
||||
source={file?.source}
|
||||
/>
|
||||
<span className="self-center truncate ">{file.filename}</span>
|
||||
</div>
|
||||
|
|
@ -84,7 +86,7 @@ export const columns: ColumnDef<TFile>[] = [
|
|||
const fileType = getFileType(file.type);
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
{fileType && <FilePreview fileType={fileType} />}
|
||||
{fileType && <FilePreview fileType={fileType} className="relative" file={file} />}
|
||||
<span className="self-center truncate">{file.filename}</span>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -108,7 +110,7 @@ export const columns: ColumnDef<TFile>[] = [
|
|||
cell: ({ row }) => formatDate(row.original.updatedAt),
|
||||
},
|
||||
{
|
||||
accessorKey: 'source',
|
||||
accessorKey: 'filterSource',
|
||||
header: ({ column }) => {
|
||||
const localize = useLocalize();
|
||||
return (
|
||||
|
|
@ -117,10 +119,14 @@ export const columns: ColumnDef<TFile>[] = [
|
|||
title={localize('com_ui_storage')}
|
||||
filters={{
|
||||
Storage: Object.values(FileSources).filter(
|
||||
(value) => value === FileSources.local || value === FileSources.openai,
|
||||
(value) =>
|
||||
value === FileSources.local ||
|
||||
value === FileSources.openai ||
|
||||
value === FileSources.azure,
|
||||
),
|
||||
}}
|
||||
valueMap={{
|
||||
[FileSources.azure]: 'Azure',
|
||||
[FileSources.openai]: 'OpenAI',
|
||||
[FileSources.local]: 'com_ui_host',
|
||||
}}
|
||||
|
|
@ -137,6 +143,13 @@ export const columns: ColumnDef<TFile>[] = [
|
|||
{'OpenAI'}
|
||||
</div>
|
||||
);
|
||||
} else if (source === FileSources.azure) {
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<AzureMinimalIcon className="icon-sm text-cyan-700" />
|
||||
{'Azure'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -48,7 +48,12 @@ const contextMap = {
|
|||
[FileContext.bytes]: 'com_ui_size',
|
||||
};
|
||||
|
||||
type Style = { width?: number | string; maxWidth?: number | string; minWidth?: number | string };
|
||||
type Style = {
|
||||
width?: number | string;
|
||||
maxWidth?: number | string;
|
||||
minWidth?: number | string;
|
||||
zIndex?: number;
|
||||
};
|
||||
|
||||
export default function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
|
||||
const localize = useLocalize();
|
||||
|
|
@ -142,7 +147,7 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
|
|||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header, index) => {
|
||||
const style: Style = { maxWidth: '32px', minWidth: '125px' };
|
||||
const style: Style = { maxWidth: '32px', minWidth: '125px', zIndex: 50 };
|
||||
if (header.id === 'filename') {
|
||||
style.maxWidth = '50%';
|
||||
style.width = '50%';
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { cn } from '~/utils';
|
|||
import { IconProps } from '~/common';
|
||||
|
||||
const MinimalIcon: React.FC<IconProps> = (props) => {
|
||||
const { size = 30, error } = props;
|
||||
const { size = 30, iconClassName, error } = props;
|
||||
|
||||
let endpoint = 'default'; // Default value for endpoint
|
||||
|
||||
|
|
@ -25,10 +25,13 @@ const MinimalIcon: React.FC<IconProps> = (props) => {
|
|||
|
||||
const endpointIcons = {
|
||||
[EModelEndpoint.azureOpenAI]: {
|
||||
icon: <AzureMinimalIcon />,
|
||||
icon: <AzureMinimalIcon className={iconClassName} />,
|
||||
name: props.chatGptLabel || 'ChatGPT',
|
||||
},
|
||||
[EModelEndpoint.openAI]: {
|
||||
icon: <OpenAIMinimalIcon className={iconClassName} />,
|
||||
name: props.chatGptLabel || 'ChatGPT',
|
||||
},
|
||||
[EModelEndpoint.openAI]: { icon: <OpenAIMinimalIcon />, name: props.chatGptLabel || 'ChatGPT' },
|
||||
[EModelEndpoint.gptPlugins]: { icon: <MinimalPlugin />, name: 'Plugins' },
|
||||
[EModelEndpoint.google]: { icon: <GoogleMinimalIcon />, name: props.modelLabel || 'Google' },
|
||||
[EModelEndpoint.anthropic]: {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: Ref<HTMLDivElement>) =
|
|||
}}
|
||||
placeholder={localize('com_nav_search_placeholder')}
|
||||
onKeyUp={handleKeyUp}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<X
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ const ApiKey = () => {
|
|||
<input
|
||||
placeholder="<HIDDEN>"
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
autoComplete="new-password"
|
||||
className="border-token-border-medium mb-2 h-9 w-full resize-none overflow-y-auto rounded-lg border px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-blue-400 dark:bg-gray-600"
|
||||
{...register('api_key', { required: type === AuthTypeEnum.ServiceHttp })}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
Tools,
|
||||
FileSources,
|
||||
Capabilities,
|
||||
EModelEndpoint,
|
||||
LocalStorageKeys,
|
||||
isImageVisionTool,
|
||||
defaultAssistantFormValues,
|
||||
|
|
@ -52,6 +53,8 @@ export default function AssistantSelect({
|
|||
const assistants = useListAssistantsQuery(endpoint, undefined, {
|
||||
select: (res) =>
|
||||
res.data.map((_assistant) => {
|
||||
const source =
|
||||
endpoint === EModelEndpoint.assistants ? FileSources.openai : FileSources.azure;
|
||||
const assistant = {
|
||||
..._assistant,
|
||||
label: _assistant?.name ?? '',
|
||||
|
|
@ -77,7 +80,7 @@ export default function AssistantSelect({
|
|||
size: file.bytes,
|
||||
preview: file.filepath,
|
||||
progress: 1,
|
||||
source: FileSources.openai,
|
||||
source,
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
|
|
@ -90,7 +93,7 @@ export default function AssistantSelect({
|
|||
size: 1,
|
||||
progress: 1,
|
||||
filepath: endpoint,
|
||||
source: FileSources.openai,
|
||||
source,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ export default function Code({
|
|||
version={version}
|
||||
endpoint={endpoint}
|
||||
files={files}
|
||||
tool_resource={Capabilities.code_interpreter}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import { useState, useRef, useEffect } from 'react';
|
||||
import { mergeFileConfig, fileConfig as defaultFileConfig } from 'librechat-data-provider';
|
||||
import {
|
||||
EToolResources,
|
||||
mergeFileConfig,
|
||||
fileConfig as defaultFileConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import type { AssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { ExtendedFile } from '~/common';
|
||||
import FileRow from '~/components/Chat/Input/Files/FileRow';
|
||||
|
|
@ -8,17 +12,17 @@ import { useFileHandling } from '~/hooks/Files';
|
|||
import useLocalize from '~/hooks/useLocalize';
|
||||
import { useChatContext } from '~/Providers';
|
||||
|
||||
export default function Knowledge({
|
||||
const tool_resource = EToolResources.code_interpreter;
|
||||
|
||||
export default function CodeFiles({
|
||||
endpoint,
|
||||
assistant_id,
|
||||
files: _files,
|
||||
tool_resource,
|
||||
}: {
|
||||
version: number | string;
|
||||
endpoint: AssistantsEndpoint;
|
||||
assistant_id: string;
|
||||
files?: [string, ExtendedFile][];
|
||||
tool_resource?: string;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { setFilesLoading } = useChatContext();
|
||||
|
|
@ -57,13 +61,14 @@ export default function Knowledge({
|
|||
<div className={'mb-2'}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-token-text-tertiary rounded-lg text-xs">
|
||||
{assistant_id ? localize('com_assistants_knowledge_info') : ''}
|
||||
{localize('com_assistants_code_interpreter_files')}
|
||||
</div>
|
||||
<FileRow
|
||||
files={files}
|
||||
setFiles={setFiles}
|
||||
setFilesLoading={setFilesLoading}
|
||||
assistant_id={assistant_id}
|
||||
tool_resource={tool_resource}
|
||||
setFilesLoading={setFilesLoading}
|
||||
Wrapper={({ children }) => <div className="flex flex-wrap gap-2">{children}</div>}
|
||||
/>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -41,10 +41,10 @@ export const AssistantAvatar = ({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<div className="relative overflow-hidden rounded-full">
|
||||
<div className="relative h-20 w-20 overflow-hidden rounded-full">
|
||||
<img
|
||||
src={url}
|
||||
className="bg-token-surface-secondary dark:bg-token-surface-tertiary h-full w-full"
|
||||
className="bg-token-surface-secondary dark:bg-token-surface-tertiary h-full w-full rounded-full object-cover"
|
||||
alt="GPT"
|
||||
width="80"
|
||||
height="80"
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ import { useFormContext, Controller, useWatch } from 'react-hook-form';
|
|||
import { Capabilities } from 'librechat-data-provider';
|
||||
import type { AssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { AssistantForm } from '~/common';
|
||||
import { Checkbox } from '~/components/ui';
|
||||
import OptionHover from '~/components/SidePanel/Parameters/OptionHover';
|
||||
import { Checkbox, HoverCard, HoverCardTrigger } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { ESide } from '~/common';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
export default function Retrieval({
|
||||
|
|
@ -21,50 +23,72 @@ export default function Retrieval({
|
|||
const model = useWatch({ control, name: 'model' });
|
||||
const assistant = useWatch({ control, name: 'assistant' });
|
||||
|
||||
const files = useMemo(() => {
|
||||
const vectorStores = useMemo(() => {
|
||||
if (typeof assistant === 'string') {
|
||||
return [];
|
||||
}
|
||||
return assistant.tool_resources?.file_search;
|
||||
}, [assistant]);
|
||||
|
||||
const isDisabled = useMemo(() => !retrievalModels.has(model), [model, retrievalModels]);
|
||||
|
||||
useEffect(() => {
|
||||
if (model && !retrievalModels.has(model)) {
|
||||
if (model && isDisabled) {
|
||||
setValue(Capabilities.retrieval, false);
|
||||
}
|
||||
}, [model, setValue, retrievalModels]);
|
||||
}, [model, setValue, isDisabled]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
name={Capabilities.retrieval}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
checked={field.value}
|
||||
disabled={!retrievalModels.has(model)}
|
||||
onCheckedChange={field.onChange}
|
||||
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
|
||||
value={field?.value?.toString()}
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
name={Capabilities.retrieval}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
checked={field.value}
|
||||
disabled={isDisabled}
|
||||
onCheckedChange={field.onChange}
|
||||
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
|
||||
value={field?.value?.toString()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<HoverCard openDelay={50}>
|
||||
<HoverCardTrigger asChild>
|
||||
<label
|
||||
className={cn(
|
||||
'form-check-label text-token-text-primary w-full select-none',
|
||||
isDisabled ? 'cursor-no-drop opacity-50' : 'cursor-pointer',
|
||||
)}
|
||||
htmlFor={Capabilities.retrieval}
|
||||
onClick={() =>
|
||||
retrievalModels.has(model) &&
|
||||
setValue(Capabilities.retrieval, !getValues(Capabilities.retrieval), {
|
||||
shouldDirty: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
{version == 1
|
||||
? localize('com_assistants_retrieval')
|
||||
: localize('com_assistants_file_search')}
|
||||
</label>
|
||||
</HoverCardTrigger>
|
||||
<OptionHover
|
||||
side={ESide.Top}
|
||||
disabled={!isDisabled}
|
||||
description="com_assistants_non_retrieval_model"
|
||||
langCode={true}
|
||||
sideOffset={20}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
className={cn(
|
||||
'form-check-label text-token-text-primary w-full',
|
||||
!retrievalModels.has(model) ? 'cursor-no-drop opacity-50' : 'cursor-pointer',
|
||||
)}
|
||||
htmlFor={Capabilities.retrieval}
|
||||
onClick={() =>
|
||||
retrievalModels.has(model) &&
|
||||
setValue(Capabilities.retrieval, !getValues(Capabilities.retrieval), {
|
||||
shouldDirty: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
{localize('com_assistants_retrieval')}
|
||||
</label>
|
||||
</div>
|
||||
</HoverCard>
|
||||
</div>
|
||||
{version == 2 && (
|
||||
<div className="text-token-text-tertiary rounded-lg text-xs">
|
||||
{localize('com_assistants_file_search_info')}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { useCallback } from 'react';
|
||||
import {
|
||||
fileConfig as defaultFileConfig,
|
||||
checkOpenAIStorage,
|
||||
mergeFileConfig,
|
||||
megabyte,
|
||||
isAssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import type { Row } from '@tanstack/react-table';
|
||||
import type { TFile } from 'librechat-data-provider';
|
||||
|
|
@ -36,6 +38,18 @@ export default function PanelFileCell({ row }: { row: Row<TFile> }) {
|
|||
return showToast({ message: localize('com_ui_attach_error'), status: 'error' });
|
||||
}
|
||||
|
||||
if (checkOpenAIStorage(fileData?.source ?? '') && !isAssistantsEndpoint(endpoint)) {
|
||||
return showToast({
|
||||
message: localize('com_ui_attach_error_openai'),
|
||||
status: 'error',
|
||||
});
|
||||
} else if (!checkOpenAIStorage(fileData?.source ?? '') && isAssistantsEndpoint(endpoint)) {
|
||||
showToast({
|
||||
message: localize('com_ui_attach_warn_endpoint'),
|
||||
status: 'warning',
|
||||
});
|
||||
}
|
||||
|
||||
const { fileSizeLimit, supportedMimeTypes } =
|
||||
fileConfig.endpoints[endpoint] ?? fileConfig.endpoints.default;
|
||||
|
||||
|
|
@ -81,7 +95,8 @@ export default function PanelFileCell({ row }: { row: Row<TFile> }) {
|
|||
>
|
||||
<ImagePreview
|
||||
url={file.filepath}
|
||||
className="h-10 w-10 shrink-0 overflow-hidden rounded-md"
|
||||
className="relative h-10 w-10 shrink-0 overflow-hidden rounded-md"
|
||||
source={file.source}
|
||||
/>
|
||||
<span className="self-center truncate text-xs">{file.filename}</span>
|
||||
</div>
|
||||
|
|
@ -94,7 +109,7 @@ export default function PanelFileCell({ row }: { row: Row<TFile> }) {
|
|||
onClick={handleFileClick}
|
||||
className="flex cursor-pointer gap-2 rounded-md dark:hover:bg-gray-700"
|
||||
>
|
||||
{fileType && <FilePreview fileType={fileType} />}
|
||||
{fileType && <FilePreview fileType={fileType} className="relative" file={file} />}
|
||||
<span className="self-center truncate">{file.filename}</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,11 +7,21 @@ type TOptionHoverProps = {
|
|||
description: string;
|
||||
langCode?: boolean;
|
||||
sideOffset?: number;
|
||||
disabled?: boolean;
|
||||
side: ESide;
|
||||
};
|
||||
|
||||
function OptionHover({ side, description, langCode, sideOffset = 30 }: TOptionHoverProps) {
|
||||
function OptionHover({
|
||||
side,
|
||||
description,
|
||||
disabled,
|
||||
langCode,
|
||||
sideOffset = 30,
|
||||
}: TOptionHoverProps) {
|
||||
const localize = useLocalize();
|
||||
if (disabled) {
|
||||
return null;
|
||||
}
|
||||
const text = langCode ? localize(description) : description;
|
||||
return (
|
||||
<HoverCardPortal>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue