mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 09:20:15 +01:00
🤖 feat: OpenAI Assistants v2 (initial support) (#2781)
* 🤖 Assistants V2 Support: Part 1 - Separated Azure Assistants to its own endpoint - File Search / Vector Store integration is incomplete, but can toggle and use storage from playground - Code Interpreter resource files can be added but not deleted - GPT-4o is supported - Many improvements to the Assistants Endpoint overall data-provider v2 changes copy existing route as v1 chore: rename new endpoint to reduce comparison operations and add new azure filesource api: add azureAssistants part 1 force use of version for assistants/assistantsAzure chore: switch name back to azureAssistants refactor type version: string | number Ensure assistants endpoints have version set fix: isArchived type issue in ConversationListParams refactor: update assistants mutations/queries with endpoint/version definitions, update Assistants Map structure chore: FilePreview component ExtendedFile type assertion feat: isAssistantsEndpoint helper chore: remove unused useGenerations chore(buildTree): type issue chore(Advanced): type issue (unused component, maybe in future) first pass for multi-assistant endpoint rewrite fix(listAssistants): pass params correctly feat: list separate assistants by endpoint fix(useTextarea): access assistantMap correctly fix: assistant endpoint switching, resetting ID fix: broken during rewrite, selecting assistant mention fix: set/invalidate assistants endpoint query data correctly feat: Fix issue with assistant ID not being reset correctly getOpenAIClient helper function feat: add toast for assistant deletion fix: assistants delete right after create issue for azure fix: assistant patching refactor: actions to use getOpenAIClient refactor: consolidate logic into helpers file fix: issue where conversation data was not initially available v1 chat support refactor(spendTokens): only early return if completionTokens isNaN fix(OpenAIClient): ensure spendTokens has all necessary params refactor: route/controller logic fix(assistants/initializeClient): use defaultHeaders field fix: sanitize default operation id chore: bump openai package first pass v2 action service feat: retroactive domain parsing for actions added via v1 feat: delete db records of actions/assistants on openai assistant deletion chore: remove vision tools from v2 assistants feat: v2 upload and delete assistant vision images WIP first pass, thread attachments fix: show assistant vision files (save local/firebase copy) v2 image continue fix: annotations fix: refine annotations show analyze as error if is no longer submitting before progress reaches 1 and show file_search as retrieval tool fix: abort run, undefined endpoint issue refactor: consolidate capabilities logic and anticipate versioning frontend version 2 changes fix: query selection and filter add endpoint to unknown filepath add file ids to resource, deleting in progress enable/disable file search remove version log * 🤖 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 * fix: merge conflict * ci: fix backend tests due to new updates * chore: update .env.example * meilisearch improvements * localization updates * chore: update comparisons * feat: add additional metadata: endpoint, author ID * chore: azureAssistants ENDPOINTS exclusion warning
This commit is contained in:
parent
af8bcb08d6
commit
1a452121fa
158 changed files with 4184 additions and 1204 deletions
|
|
@ -4,7 +4,11 @@ import type { Option, ExtendedFile } from './types';
|
|||
|
||||
export type TAssistantOption =
|
||||
| string
|
||||
| (Option & Assistant & { files?: Array<[string, ExtendedFile]> });
|
||||
| (Option &
|
||||
Assistant & {
|
||||
files?: Array<[string, ExtendedFile]>;
|
||||
code_files?: Array<[string, ExtendedFile]>;
|
||||
});
|
||||
|
||||
export type Actions = {
|
||||
[Capabilities.code_interpreter]: boolean;
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ import type {
|
|||
TPreset,
|
||||
TPlugin,
|
||||
TMessage,
|
||||
Assistant,
|
||||
TLoginUser,
|
||||
AuthTypeEnum,
|
||||
TConversation,
|
||||
EModelEndpoint,
|
||||
AssistantsEndpoint,
|
||||
AuthorizationTypeEnum,
|
||||
TSetOption as SetOption,
|
||||
TokenExchangeMethodEnum,
|
||||
|
|
@ -19,6 +21,13 @@ import type {
|
|||
import type { UseMutationResult } from '@tanstack/react-query';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
|
||||
export type AssistantListItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
metadata: Assistant['metadata'];
|
||||
model: string;
|
||||
};
|
||||
|
||||
export type TPluginMap = Record<string, TPlugin>;
|
||||
|
||||
export type GenericSetter<T> = (value: T | ((currentValue: T) => T)) => void;
|
||||
|
|
@ -101,6 +110,8 @@ export type AssistantPanelProps = {
|
|||
actions?: Action[];
|
||||
assistant_id?: string;
|
||||
activePanel?: string;
|
||||
endpoint: AssistantsEndpoint;
|
||||
version: number | string;
|
||||
setAction: React.Dispatch<React.SetStateAction<Action | undefined>>;
|
||||
setCurrentAssistantId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
setActivePanel: React.Dispatch<React.SetStateAction<Panel>>;
|
||||
|
|
@ -315,6 +326,7 @@ export type IconProps = Pick<TMessage, 'isCreatedByUser' | 'model'> &
|
|||
iconURL?: string;
|
||||
message?: boolean;
|
||||
className?: string;
|
||||
iconClassName?: string;
|
||||
endpoint?: EModelEndpoint | string | null;
|
||||
endpointType?: EModelEndpoint | null;
|
||||
assistantName?: string;
|
||||
|
|
@ -327,7 +339,11 @@ export type Option = Record<string, unknown> & {
|
|||
};
|
||||
|
||||
export type OptionWithIcon = Option & { icon?: React.ReactNode };
|
||||
export type MentionOption = OptionWithIcon & { type: string; value: string; description?: string };
|
||||
export type MentionOption = OptionWithIcon & {
|
||||
type: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type TOptionSettings = {
|
||||
showExamples?: boolean;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { useForm } from 'react-hook-form';
|
|||
import { memo, useCallback, useRef, useMemo } from 'react';
|
||||
import {
|
||||
supportsFiles,
|
||||
EModelEndpoint,
|
||||
mergeFileConfig,
|
||||
isAssistantsEndpoint,
|
||||
fileConfig as defaultFileConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
||||
|
|
@ -74,8 +74,9 @@ const ChatForm = ({ index = 0 }) => {
|
|||
const endpointFileConfig = fileConfig.endpoints[endpoint ?? ''];
|
||||
const invalidAssistant = useMemo(
|
||||
() =>
|
||||
conversation?.endpoint === EModelEndpoint.assistants &&
|
||||
(!conversation?.assistant_id || !assistantMap?.[conversation?.assistant_id ?? '']),
|
||||
isAssistantsEndpoint(conversation?.endpoint) &&
|
||||
(!conversation?.assistant_id ||
|
||||
!assistantMap?.[conversation?.endpoint ?? '']?.[conversation?.assistant_id ?? '']),
|
||||
[conversation?.assistant_id, conversation?.endpoint, assistantMap],
|
||||
);
|
||||
const disableInputs = useMemo(
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
@ -20,8 +21,7 @@ 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?.size ?? 1);
|
||||
console.log(progress);
|
||||
const progress = useProgress(file?.['progress'] ?? 1, 0.001, (file as ExtendedFile)?.size ?? 1);
|
||||
|
||||
// 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%';
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ export default function Mention({
|
|||
}) {
|
||||
const localize = useLocalize();
|
||||
const assistantMap = useAssistantsMapContext();
|
||||
const { options, modelsConfig, assistants, onSelectMention } = useMentions({ assistantMap });
|
||||
const { options, modelsConfig, assistantListMap, onSelectMention } = useMentions({
|
||||
assistantMap,
|
||||
});
|
||||
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
|
@ -47,7 +49,12 @@ export default function Mention({
|
|||
|
||||
if (mention.type === 'endpoint' && mention.value === EModelEndpoint.assistants) {
|
||||
setSearchValue('');
|
||||
setInputOptions(assistants);
|
||||
setInputOptions(assistantListMap[EModelEndpoint.assistants]);
|
||||
setActiveIndex(0);
|
||||
inputRef.current?.focus();
|
||||
} else if (mention.type === 'endpoint' && mention.value === EModelEndpoint.azureAssistants) {
|
||||
setSearchValue('');
|
||||
setInputOptions(assistantListMap[EModelEndpoint.azureAssistants]);
|
||||
setActiveIndex(0);
|
||||
inputRef.current?.focus();
|
||||
} else if (mention.type === 'endpoint') {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery, useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||
import type { ReactNode } from 'react';
|
||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
|
||||
|
|
@ -30,7 +30,8 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
|
|||
const iconURL = conversation?.iconURL;
|
||||
endpoint = getIconEndpoint({ endpointsConfig, iconURL, endpoint });
|
||||
|
||||
const assistant = endpoint === EModelEndpoint.assistants && assistantMap?.[assistant_id ?? ''];
|
||||
const isAssistant = isAssistantsEndpoint(endpoint);
|
||||
const assistant = isAssistant && assistantMap?.[endpoint]?.[assistant_id ?? ''];
|
||||
const assistantName = (assistant && assistant?.name) || '';
|
||||
const assistantDesc = (assistant && assistant?.description) || '';
|
||||
const avatar = (assistant && (assistant?.metadata?.avatar as string)) || '';
|
||||
|
|
@ -77,7 +78,7 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
|
|||
</div>
|
||||
) : (
|
||||
<div className="mb-5 max-w-[75vh] px-12 text-center text-lg font-medium dark:text-white md:px-0 md:text-2xl">
|
||||
{endpoint === EModelEndpoint.assistants
|
||||
{isAssistant
|
||||
? conversation?.greeting ?? localize('com_nav_welcome_assistant')
|
||||
: conversation?.greeting ?? localize('com_nav_welcome_message')}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,24 @@ import {
|
|||
import UnknownIcon from './UnknownIcon';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const AssistantAvatar = ({ className = '', assistantName, avatar, size }: IconMapProps) => {
|
||||
if (assistantName && avatar) {
|
||||
return (
|
||||
<img
|
||||
src={avatar}
|
||||
className="bg-token-surface-secondary dark:bg-token-surface-tertiary h-full w-full rounded-full object-cover"
|
||||
alt={assistantName}
|
||||
width="80"
|
||||
height="80"
|
||||
/>
|
||||
);
|
||||
} else if (assistantName) {
|
||||
return <AssistantIcon className={cn('text-token-secondary', className)} size={size} />;
|
||||
}
|
||||
|
||||
return <Sparkles className={cn(assistantName === '' ? 'icon-2xl' : '', className)} />;
|
||||
};
|
||||
|
||||
export const icons = {
|
||||
[EModelEndpoint.azureOpenAI]: AzureMinimalIcon,
|
||||
[EModelEndpoint.openAI]: GPTIcon,
|
||||
|
|
@ -24,22 +42,7 @@ export const icons = {
|
|||
[EModelEndpoint.google]: GoogleMinimalIcon,
|
||||
[EModelEndpoint.bingAI]: BingAIMinimalIcon,
|
||||
[EModelEndpoint.custom]: CustomMinimalIcon,
|
||||
[EModelEndpoint.assistants]: ({ className = '', assistantName, avatar, size }: IconMapProps) => {
|
||||
if (assistantName && avatar) {
|
||||
return (
|
||||
<img
|
||||
src={avatar}
|
||||
className="bg-token-surface-secondary dark:bg-token-surface-tertiary h-full w-full rounded-full object-cover"
|
||||
alt={assistantName}
|
||||
width="80"
|
||||
height="80"
|
||||
/>
|
||||
);
|
||||
} else if (assistantName) {
|
||||
return <AssistantIcon className={cn('text-token-secondary', className)} size={size} />;
|
||||
}
|
||||
|
||||
return <Sparkles className={cn(assistantName === '' ? 'icon-2xl' : '', className)} />;
|
||||
},
|
||||
[EModelEndpoint.assistants]: AssistantAvatar,
|
||||
[EModelEndpoint.azureAssistants]: AssistantAvatar,
|
||||
unknown: UnknownIcon,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Content, Portal, Root } from '@radix-ui/react-popover';
|
||||
import { alternateName, EModelEndpoint } from 'librechat-data-provider';
|
||||
import { alternateName, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
||||
import type { FC } from 'react';
|
||||
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
||||
|
|
@ -16,7 +16,8 @@ const EndpointsMenu: FC = () => {
|
|||
const { endpoint = '', assistant_id = null } = conversation ?? {};
|
||||
const assistantMap = useAssistantsMapContext();
|
||||
|
||||
const assistant = endpoint === EModelEndpoint.assistants && assistantMap?.[assistant_id ?? ''];
|
||||
const assistant =
|
||||
isAssistantsEndpoint(endpoint) && assistantMap?.[endpoint ?? '']?.[assistant_id ?? ''];
|
||||
const assistantName = (assistant && assistant?.name) || 'Assistant';
|
||||
|
||||
if (!endpoint) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import ProgressCircle from './ProgressCircle';
|
||||
import CancelledIcon from './CancelledIcon';
|
||||
import ProgressText from './ProgressText';
|
||||
import FinishedIcon from './FinishedIcon';
|
||||
import MarkdownLite from './MarkdownLite';
|
||||
|
|
@ -11,10 +12,12 @@ export default function CodeAnalyze({
|
|||
initialProgress = 0.1,
|
||||
code,
|
||||
outputs = [],
|
||||
isSubmitting,
|
||||
}: {
|
||||
initialProgress: number;
|
||||
code: string;
|
||||
outputs: Record<string, unknown>[];
|
||||
isSubmitting: boolean;
|
||||
}) {
|
||||
const showCodeDefault = useRecoilValue(store.showCode);
|
||||
const [showCode, setShowCode] = useState(showCodeDefault);
|
||||
|
|
@ -35,7 +38,13 @@ export default function CodeAnalyze({
|
|||
<div className="my-2.5 flex items-center gap-2.5">
|
||||
<div className="relative h-5 w-5 shrink-0">
|
||||
{progress < 1 ? (
|
||||
<CodeInProgress offset={offset} circumference={circumference} radius={radius} />
|
||||
<CodeInProgress
|
||||
offset={offset}
|
||||
radius={radius}
|
||||
progress={progress}
|
||||
isSubmitting={isSubmitting}
|
||||
circumference={circumference}
|
||||
/>
|
||||
) : (
|
||||
<FinishedIcon />
|
||||
)}
|
||||
|
|
@ -74,18 +83,25 @@ const CodeInProgress = ({
|
|||
offset,
|
||||
circumference,
|
||||
radius,
|
||||
isSubmitting,
|
||||
progress,
|
||||
}: {
|
||||
progress: number;
|
||||
offset: number;
|
||||
circumference: number;
|
||||
radius: number;
|
||||
isSubmitting: boolean;
|
||||
}) => {
|
||||
if (progress < 1 && !isSubmitting) {
|
||||
return <CancelledIcon />;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="absolute left-0 top-0 flex h-full w-full items-center justify-center rounded-full bg-transparent text-white"
|
||||
style={{ opacity: 1, transform: 'none' }}
|
||||
data-projection-id="77"
|
||||
>
|
||||
<div className='absolute right-[1.5px] bottom-[1.5px]'>
|
||||
<div className="absolute bottom-[1.5px] right-[1.5px]">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
|
|
|
|||
|
|
@ -79,11 +79,13 @@ export default function Part({
|
|||
initialProgress={toolCall.progress ?? 0.1}
|
||||
code={code_interpreter.input}
|
||||
outputs={code_interpreter.outputs ?? []}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
part.type === ContentTypes.TOOL_CALL &&
|
||||
part[ContentTypes.TOOL_CALL].type === ToolCallTypes.RETRIEVAL
|
||||
(part[ContentTypes.TOOL_CALL].type === ToolCallTypes.RETRIEVAL ||
|
||||
part[ContentTypes.TOOL_CALL].type === ToolCallTypes.FILE_SEARCH)
|
||||
) {
|
||||
const toolCall = part[ContentTypes.TOOL_CALL];
|
||||
return <RetrievalCall initialProgress={toolCall.progress ?? 0.1} isSubmitting={isSubmitting} />;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useState } from 'react';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { TConversation, TMessage } from 'librechat-data-provider';
|
||||
import { Clipboard, CheckMark, EditIcon, RegenerateIcon, ContinueIcon } from '~/components/svg';
|
||||
import { useGenerationsByLatest, useLocalize } from '~/hooks';
|
||||
|
|
@ -35,14 +34,19 @@ export default function HoverButtons({
|
|||
const { endpoint: _endpoint, endpointType } = conversation ?? {};
|
||||
const endpoint = endpointType ?? _endpoint;
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const { hideEditButton, regenerateEnabled, continueSupported, forkingSupported } =
|
||||
useGenerationsByLatest({
|
||||
isEditing,
|
||||
isSubmitting,
|
||||
message,
|
||||
endpoint: endpoint ?? '',
|
||||
latestMessage,
|
||||
});
|
||||
const {
|
||||
hideEditButton,
|
||||
regenerateEnabled,
|
||||
continueSupported,
|
||||
forkingSupported,
|
||||
isEditableEndpoint,
|
||||
} = useGenerationsByLatest({
|
||||
isEditing,
|
||||
isSubmitting,
|
||||
message,
|
||||
endpoint: endpoint ?? '',
|
||||
latestMessage,
|
||||
});
|
||||
if (!conversation) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -58,7 +62,7 @@ export default function HoverButtons({
|
|||
|
||||
return (
|
||||
<div className="visible mt-0 flex justify-center gap-1 self-end text-gray-400 lg:justify-start">
|
||||
{endpoint !== EModelEndpoint.assistants && (
|
||||
{isEditableEndpoint && (
|
||||
<button
|
||||
className={cn(
|
||||
'hover-button rounded-md p-1 text-gray-400 hover:text-gray-900 dark:text-gray-400/70 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { Assistant, TConversation, TEndpointsConfig, TPreset } from 'librechat-data-provider';
|
||||
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type {
|
||||
TAssistantsMap,
|
||||
TConversation,
|
||||
TEndpointsConfig,
|
||||
TPreset,
|
||||
} from 'librechat-data-provider';
|
||||
import { getEndpointField, getIconKey, getIconEndpoint } from '~/utils';
|
||||
import { icons } from '~/components/Chat/Menus/Endpoints/Icons';
|
||||
import ConvoIconURL from '~/components/Endpoints/ConvoIconURL';
|
||||
import { getEndpointField, getIconKey, getIconEndpoint } from '~/utils';
|
||||
|
||||
export default function ConvoIcon({
|
||||
conversation,
|
||||
|
|
@ -15,7 +20,7 @@ export default function ConvoIcon({
|
|||
}: {
|
||||
conversation: TConversation | TPreset | null;
|
||||
endpointsConfig: TEndpointsConfig;
|
||||
assistantMap: Record<string, Assistant>;
|
||||
assistantMap: TAssistantsMap;
|
||||
containerClassName?: string;
|
||||
context?: 'message' | 'nav' | 'landing' | 'menu-item';
|
||||
className?: string;
|
||||
|
|
@ -25,7 +30,7 @@ export default function ConvoIcon({
|
|||
let endpoint = conversation?.endpoint;
|
||||
endpoint = getIconEndpoint({ endpointsConfig, iconURL, endpoint });
|
||||
const assistant =
|
||||
endpoint === EModelEndpoint.assistants && assistantMap?.[conversation?.assistant_id ?? ''];
|
||||
isAssistantsEndpoint(endpoint) && assistantMap?.[endpoint]?.[conversation?.assistant_id ?? ''];
|
||||
const assistantName = (assistant && assistant?.name) || '';
|
||||
|
||||
const avatar = (assistant && (assistant?.metadata?.avatar as string)) || '';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { Assistant, TConversation, TEndpointsConfig, TPreset } from 'librechat-data-provider';
|
||||
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type {
|
||||
TConversation,
|
||||
TEndpointsConfig,
|
||||
TPreset,
|
||||
TAssistantsMap,
|
||||
} from 'librechat-data-provider';
|
||||
import ConvoIconURL from '~/components/Endpoints/ConvoIconURL';
|
||||
import MinimalIcon from '~/components/Endpoints/MinimalIcon';
|
||||
import { getEndpointField, getIconEndpoint } from '~/utils';
|
||||
|
|
@ -15,7 +20,7 @@ export default function EndpointIcon({
|
|||
endpointsConfig: TEndpointsConfig;
|
||||
containerClassName?: string;
|
||||
context?: 'message' | 'nav' | 'landing' | 'menu-item';
|
||||
assistantMap?: Record<string, Assistant>;
|
||||
assistantMap?: TAssistantsMap;
|
||||
className?: string;
|
||||
size?: number;
|
||||
}) {
|
||||
|
|
@ -27,7 +32,7 @@ export default function EndpointIcon({
|
|||
const endpointIconURL = getEndpointField(endpointsConfig, endpoint, 'iconURL');
|
||||
|
||||
const assistant =
|
||||
endpoint === EModelEndpoint.assistants && assistantMap?.[conversation?.assistant_id ?? ''];
|
||||
isAssistantsEndpoint(endpoint) && assistantMap?.[endpoint]?.[conversation?.assistant_id ?? ''];
|
||||
const assistantAvatar = (assistant && (assistant?.metadata?.avatar as string)) || '';
|
||||
const assistantName = (assistant && assistant?.name) || '';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import UnknownIcon from '~/components/Chat/Menus/Endpoints/UnknownIcon';
|
||||
import {
|
||||
Plugin,
|
||||
|
|
@ -27,35 +27,38 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
|||
assistantName,
|
||||
} = props;
|
||||
|
||||
const assistantsIcon = {
|
||||
icon: props.iconURL ? (
|
||||
<div className="relative flex h-6 w-6 items-center justify-center">
|
||||
<div
|
||||
title={assistantName}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
}}
|
||||
className={cn('overflow-hidden rounded-full', props.className ?? '')}
|
||||
>
|
||||
<img
|
||||
className="shadow-stroke h-full w-full object-cover"
|
||||
src={props.iconURL}
|
||||
alt={assistantName}
|
||||
style={{ height: '80', width: '80' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-6 w-6">
|
||||
<div className="shadow-stroke flex h-6 w-6 items-center justify-center overflow-hidden rounded-full">
|
||||
<AssistantIcon className="h-2/3 w-2/3 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
name: endpoint,
|
||||
};
|
||||
|
||||
const endpointIcons = {
|
||||
[EModelEndpoint.assistants]: {
|
||||
icon: props.iconURL ? (
|
||||
<div className="relative flex h-6 w-6 items-center justify-center">
|
||||
<div
|
||||
title={assistantName}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
}}
|
||||
className={cn('overflow-hidden rounded-full', props.className ?? '')}
|
||||
>
|
||||
<img
|
||||
className="shadow-stroke h-full w-full object-cover"
|
||||
src={props.iconURL}
|
||||
alt={assistantName}
|
||||
style={{ height: '80', width: '80' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-6 w-6">
|
||||
<div className="shadow-stroke flex h-6 w-6 items-center justify-center overflow-hidden rounded-full">
|
||||
<AssistantIcon className="h-2/3 w-2/3 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
name: endpoint,
|
||||
},
|
||||
[EModelEndpoint.assistants]: assistantsIcon,
|
||||
[EModelEndpoint.azureAssistants]: assistantsIcon,
|
||||
[EModelEndpoint.azureOpenAI]: {
|
||||
icon: <AzureMinimalIcon size={size * 0.5555555555555556} />,
|
||||
bg: 'linear-gradient(0.375turn, #61bde2, #4389d0)',
|
||||
|
|
@ -136,7 +139,7 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
|||
({ icon, bg, name } = endpointIcons[iconURL]);
|
||||
}
|
||||
|
||||
if (endpoint === EModelEndpoint.assistants) {
|
||||
if (isAssistantsEndpoint(endpoint)) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]: {
|
||||
|
|
@ -42,6 +45,7 @@ const MinimalIcon: React.FC<IconProps> = (props) => {
|
|||
[EModelEndpoint.bingAI]: { icon: <BingAIMinimalIcon />, name: 'BingAI' },
|
||||
[EModelEndpoint.chatGPTBrowser]: { icon: <LightningIcon />, name: 'ChatGPT' },
|
||||
[EModelEndpoint.assistants]: { icon: <Sparkles className="icon-sm" />, name: 'Assistant' },
|
||||
[EModelEndpoint.azureAssistants]: { icon: <Sparkles className="icon-sm" />, name: 'Assistant' },
|
||||
default: {
|
||||
icon: (
|
||||
<UnknownIcon
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { ImageDetail, imageDetailNumeric, imageDetailValue } from 'librechat-data-provider';
|
||||
import type { ValueType } from '@rc-component/mini-decimal';
|
||||
import type { TModelSelectProps } from '~/common';
|
||||
import {
|
||||
Input,
|
||||
Label,
|
||||
|
|
@ -11,7 +13,6 @@ import {
|
|||
} from '~/components/ui';
|
||||
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils/';
|
||||
import { useLocalize, useDebouncedInput } from '~/hooks';
|
||||
import type { TModelSelectProps } from '~/common';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
|
||||
|
|
@ -127,7 +128,7 @@ export default function Settings({
|
|||
id="temp-int"
|
||||
disabled={readonly}
|
||||
value={temperatureValue as number}
|
||||
onChange={setTemperature}
|
||||
onChange={setTemperature as (value: ValueType | null) => void}
|
||||
max={2}
|
||||
min={0}
|
||||
step={0.01}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import { useState, useMemo, useEffect } from 'react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { defaultOrderQuery } from 'librechat-data-provider';
|
||||
import type { TPreset } from 'librechat-data-provider';
|
||||
import type { TModelSelectProps, Option } from '~/common';
|
||||
import { Label, HoverCard, SelectDropDown, HoverCardTrigger } from '~/components/ui';
|
||||
import { cn, defaultTextProps, removeFocusOutlines, mapAssistants } from '~/utils';
|
||||
import { useLocalize, useDebouncedInput } from '~/hooks';
|
||||
import { useListAssistantsQuery } from '~/data-provider';
|
||||
import { useLocalize, useDebouncedInput, useAssistantListMap } from '~/hooks';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
|
||||
|
|
@ -17,23 +15,21 @@ export default function Settings({ conversation, setOption, models, readonly }:
|
|||
[localize],
|
||||
);
|
||||
|
||||
const { data: assistants = [] } = useListAssistantsQuery(defaultOrderQuery, {
|
||||
select: (res) =>
|
||||
[
|
||||
defaultOption,
|
||||
...res.data.map(({ id, name }) => ({
|
||||
label: name,
|
||||
value: id,
|
||||
})),
|
||||
].filter(Boolean),
|
||||
});
|
||||
|
||||
const { data: assistantMap = {} } = useListAssistantsQuery(defaultOrderQuery, {
|
||||
select: (res) => mapAssistants(res.data),
|
||||
});
|
||||
const assistantListMap = useAssistantListMap((res) => mapAssistants(res.data));
|
||||
|
||||
const { model, endpoint, assistant_id, endpointType, promptPrefix, instructions } =
|
||||
conversation ?? {};
|
||||
|
||||
const assistants = useMemo(() => {
|
||||
return [
|
||||
defaultOption,
|
||||
...(assistantListMap[endpoint ?? ''] ?? []).map(({ id, name }) => ({
|
||||
label: name,
|
||||
value: id,
|
||||
})),
|
||||
].filter(Boolean);
|
||||
}, [assistantListMap, endpoint, defaultOption]);
|
||||
|
||||
const [onPromptPrefixChange, promptPrefixValue] = useDebouncedInput({
|
||||
setOption,
|
||||
optionKey: 'promptPrefix',
|
||||
|
|
@ -47,11 +43,11 @@ export default function Settings({ conversation, setOption, models, readonly }:
|
|||
|
||||
const activeAssistant = useMemo(() => {
|
||||
if (assistant_id) {
|
||||
return assistantMap[assistant_id];
|
||||
return assistantListMap[endpoint ?? '']?.[assistant_id];
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [assistant_id, assistantMap]);
|
||||
}, [assistant_id, assistantListMap, endpoint]);
|
||||
|
||||
const modelOptions = useMemo(() => {
|
||||
return models.map((model) => ({
|
||||
|
|
@ -89,7 +85,7 @@ export default function Settings({ conversation, setOption, models, readonly }:
|
|||
return;
|
||||
}
|
||||
|
||||
const assistant = assistantMap[value];
|
||||
const assistant = assistantListMap[endpoint ?? '']?.[value];
|
||||
if (!assistant) {
|
||||
setAssistantValue(defaultOption);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import OpenAISettings from './OpenAI';
|
|||
|
||||
const settings: { [key: string]: FC<TModelSelectProps> } = {
|
||||
[EModelEndpoint.assistants]: AssistantsSettings,
|
||||
[EModelEndpoint.azureAssistants]: AssistantsSettings,
|
||||
[EModelEndpoint.openAI]: OpenAISettings,
|
||||
[EModelEndpoint.custom]: OpenAISettings,
|
||||
[EModelEndpoint.azureOpenAI]: OpenAISettings,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useForm, FormProvider } from 'react-hook-form';
|
||||
import { EModelEndpoint, alternateName } from 'librechat-data-provider';
|
||||
import { EModelEndpoint, alternateName, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
||||
import type { TDialogProps } from '~/common';
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||
|
|
@ -21,6 +21,7 @@ const endpointComponents = {
|
|||
[EModelEndpoint.azureOpenAI]: OpenAIConfig,
|
||||
[EModelEndpoint.gptPlugins]: OpenAIConfig,
|
||||
[EModelEndpoint.assistants]: OpenAIConfig,
|
||||
[EModelEndpoint.azureAssistants]: OpenAIConfig,
|
||||
default: OtherConfig,
|
||||
};
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ const formSet: Set<string> = new Set([
|
|||
EModelEndpoint.azureOpenAI,
|
||||
EModelEndpoint.gptPlugins,
|
||||
EModelEndpoint.assistants,
|
||||
EModelEndpoint.azureAssistants,
|
||||
]);
|
||||
|
||||
const EXPIRY = {
|
||||
|
|
@ -97,7 +99,7 @@ const SetKeyDialog = ({
|
|||
isAzure ||
|
||||
endpoint === EModelEndpoint.openAI ||
|
||||
endpoint === EModelEndpoint.gptPlugins ||
|
||||
endpoint === EModelEndpoint.assistants;
|
||||
isAssistantsEndpoint(endpoint);
|
||||
if (isAzure) {
|
||||
data.apiKey = 'n/a';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useEffect, useMemo } from 'react';
|
||||
import { Combobox } from '~/components/ui';
|
||||
import { EModelEndpoint, defaultOrderQuery, LocalStorageKeys } from 'librechat-data-provider';
|
||||
import type { SwitcherProps } from '~/common';
|
||||
import { useSetIndexOptions, useSelectAssistant, useLocalize } from '~/hooks';
|
||||
import { isAssistantsEndpoint, LocalStorageKeys } from 'librechat-data-provider';
|
||||
import type { AssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { SwitcherProps, AssistantListItem } from '~/common';
|
||||
import { useSetIndexOptions, useSelectAssistant, useLocalize, useAssistantListMap } from '~/hooks';
|
||||
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
||||
import { useListAssistantsQuery } from '~/data-provider';
|
||||
import Icon from '~/components/Endpoints/Icon';
|
||||
|
||||
export default function AssistantSwitcher({ isCollapsed }: SwitcherProps) {
|
||||
|
|
@ -15,26 +15,29 @@ export default function AssistantSwitcher({ isCollapsed }: SwitcherProps) {
|
|||
/* `selectedAssistant` must be defined with `null` to cause re-render on update */
|
||||
const { assistant_id: selectedAssistant = null, endpoint } = conversation ?? {};
|
||||
|
||||
const { data: assistants = [] } = useListAssistantsQuery(defaultOrderQuery, {
|
||||
select: (res) => res.data.map(({ id, name, metadata }) => ({ id, name, metadata })),
|
||||
});
|
||||
|
||||
const assistantListMap = useAssistantListMap((res) =>
|
||||
res.data.map(({ id, name, metadata }) => ({ id, name, metadata })),
|
||||
);
|
||||
const assistants: Omit<AssistantListItem, 'model'>[] = useMemo(
|
||||
() => assistantListMap[endpoint ?? ''] ?? [],
|
||||
[endpoint, assistantListMap],
|
||||
);
|
||||
const assistantMap = useAssistantsMapContext();
|
||||
const { onSelect } = useSelectAssistant();
|
||||
const { onSelect } = useSelectAssistant(endpoint as AssistantsEndpoint);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedAssistant && assistants && assistants.length && assistantMap) {
|
||||
const assistant_id =
|
||||
localStorage.getItem(`${LocalStorageKeys.ASST_ID_PREFIX}${index}`) ??
|
||||
localStorage.getItem(`${LocalStorageKeys.ASST_ID_PREFIX}${index}${endpoint}`) ??
|
||||
assistants[0]?.id ??
|
||||
'';
|
||||
const assistant = assistantMap?.[assistant_id];
|
||||
const assistant = assistantMap?.[endpoint ?? '']?.[assistant_id];
|
||||
|
||||
if (!assistant) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (endpoint !== EModelEndpoint.assistants) {
|
||||
if (!isAssistantsEndpoint(endpoint)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +46,7 @@ export default function AssistantSwitcher({ isCollapsed }: SwitcherProps) {
|
|||
}
|
||||
}, [index, assistants, selectedAssistant, assistantMap, endpoint, setOption]);
|
||||
|
||||
const currentAssistant = assistantMap?.[selectedAssistant ?? ''];
|
||||
const currentAssistant = assistantMap?.[endpoint ?? '']?.[selectedAssistant ?? ''];
|
||||
|
||||
const assistantOptions = useMemo(() => {
|
||||
return assistants.map((assistant) => {
|
||||
|
|
@ -53,14 +56,14 @@ export default function AssistantSwitcher({ isCollapsed }: SwitcherProps) {
|
|||
icon: (
|
||||
<Icon
|
||||
isCreatedByUser={false}
|
||||
endpoint={EModelEndpoint.assistants}
|
||||
endpoint={endpoint}
|
||||
assistantName={assistant.name ?? ''}
|
||||
iconURL={(assistant.metadata?.avatar as string) ?? ''}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
}, [assistants]);
|
||||
}, [assistants, endpoint]);
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
|
|
@ -78,7 +81,7 @@ export default function AssistantSwitcher({ isCollapsed }: SwitcherProps) {
|
|||
SelectIcon={
|
||||
<Icon
|
||||
isCreatedByUser={false}
|
||||
endpoint={EModelEndpoint.assistants}
|
||||
endpoint={endpoint}
|
||||
assistantName={currentAssistant?.name ?? ''}
|
||||
iconURL={(currentAssistant?.metadata?.avatar as string) ?? ''}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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 })}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@ import {
|
|||
AuthTypeEnum,
|
||||
} from 'librechat-data-provider';
|
||||
import type {
|
||||
ValidationResult,
|
||||
Action,
|
||||
FunctionTool,
|
||||
ActionMetadata,
|
||||
ValidationResult,
|
||||
AssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import type { ActionAuthForm } from '~/common';
|
||||
import type { Spec } from './ActionsTable';
|
||||
|
|
@ -32,10 +33,14 @@ const debouncedValidation = debounce(
|
|||
export default function ActionsInput({
|
||||
action,
|
||||
assistant_id,
|
||||
endpoint,
|
||||
version,
|
||||
setAction,
|
||||
}: {
|
||||
action?: Action;
|
||||
assistant_id?: string;
|
||||
endpoint: AssistantsEndpoint;
|
||||
version: number | string;
|
||||
setAction: React.Dispatch<React.SetStateAction<Action | undefined>>;
|
||||
}) {
|
||||
const handleResult = (result: ValidationResult) => {
|
||||
|
|
@ -173,7 +178,9 @@ export default function ActionsInput({
|
|||
metadata,
|
||||
functions,
|
||||
assistant_id,
|
||||
model: assistantMap[assistant_id].model,
|
||||
endpoint,
|
||||
version,
|
||||
model: assistantMap[endpoint][assistant_id].model,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,11 @@ import { Panel } from '~/common';
|
|||
export default function ActionsPanel({
|
||||
// activePanel,
|
||||
action,
|
||||
endpoint,
|
||||
version,
|
||||
setAction,
|
||||
setActivePanel,
|
||||
assistant_id,
|
||||
setActivePanel,
|
||||
}: AssistantPanelProps) {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
|
|
@ -130,9 +132,10 @@ export default function ActionsPanel({
|
|||
const confirmed = confirm('Are you sure you want to delete this action?');
|
||||
if (confirmed) {
|
||||
deleteAction.mutate({
|
||||
model: assistantMap[assistant_id].model,
|
||||
model: assistantMap[endpoint][assistant_id].model,
|
||||
action_id: action.action_id,
|
||||
assistant_id,
|
||||
endpoint,
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
|
@ -185,7 +188,13 @@ export default function ActionsPanel({
|
|||
</DialogTrigger>
|
||||
<ActionsAuth setOpenAuthDialog={setOpenAuthDialog} />
|
||||
</Dialog>
|
||||
<ActionsInput action={action} assistant_id={assistant_id} setAction={setAction} />
|
||||
<ActionsInput
|
||||
action={action}
|
||||
assistant_id={assistant_id}
|
||||
setAction={setAction}
|
||||
endpoint={endpoint}
|
||||
version={version}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@ import {
|
|||
import type { UseMutationResult } from '@tanstack/react-query';
|
||||
import type {
|
||||
Metadata,
|
||||
AssistantListResponse,
|
||||
Assistant,
|
||||
AssistantsEndpoint,
|
||||
AssistantCreateParams,
|
||||
AssistantListResponse,
|
||||
} from 'librechat-data-provider';
|
||||
import { useUploadAssistantAvatarMutation, useGetFileConfig } from '~/data-provider';
|
||||
import { AssistantAvatar, NoImage, AvatarMenu } from './Images';
|
||||
|
|
@ -22,10 +23,14 @@ import { useLocalize } from '~/hooks';
|
|||
// import { cn } from '~/utils/';
|
||||
|
||||
function Avatar({
|
||||
endpoint,
|
||||
version,
|
||||
assistant_id,
|
||||
metadata,
|
||||
createMutation,
|
||||
}: {
|
||||
endpoint: AssistantsEndpoint;
|
||||
version: number | string;
|
||||
assistant_id: string | null;
|
||||
metadata: null | Metadata;
|
||||
createMutation: UseMutationResult<Assistant, Error, AssistantCreateParams>;
|
||||
|
|
@ -46,8 +51,8 @@ function Avatar({
|
|||
const { showToast } = useToastContext();
|
||||
|
||||
const activeModel = useMemo(() => {
|
||||
return assistantsMap[assistant_id ?? '']?.model ?? '';
|
||||
}, [assistant_id, assistantsMap]);
|
||||
return assistantsMap[endpoint][assistant_id ?? '']?.model ?? '';
|
||||
}, [assistantsMap, endpoint, assistant_id]);
|
||||
|
||||
const { mutate: uploadAvatar } = useUploadAssistantAvatarMutation({
|
||||
onMutate: () => {
|
||||
|
|
@ -65,6 +70,7 @@ function Avatar({
|
|||
|
||||
const res = queryClient.getQueryData<AssistantListResponse>([
|
||||
QueryKeys.assistants,
|
||||
endpoint,
|
||||
defaultOrderQuery,
|
||||
]);
|
||||
|
||||
|
|
@ -83,10 +89,13 @@ function Avatar({
|
|||
return assistant;
|
||||
}) ?? [];
|
||||
|
||||
queryClient.setQueryData<AssistantListResponse>([QueryKeys.assistants, defaultOrderQuery], {
|
||||
...res,
|
||||
data: assistants,
|
||||
});
|
||||
queryClient.setQueryData<AssistantListResponse>(
|
||||
[QueryKeys.assistants, endpoint, defaultOrderQuery],
|
||||
{
|
||||
...res,
|
||||
data: assistants,
|
||||
},
|
||||
);
|
||||
|
||||
setProgress(1);
|
||||
},
|
||||
|
|
@ -149,9 +158,20 @@ function Avatar({
|
|||
model: activeModel,
|
||||
postCreation: true,
|
||||
formData,
|
||||
endpoint,
|
||||
version,
|
||||
});
|
||||
}
|
||||
}, [createMutation.data, createMutation.isSuccess, input, previewUrl, uploadAvatar, activeModel]);
|
||||
}, [
|
||||
createMutation.data,
|
||||
createMutation.isSuccess,
|
||||
input,
|
||||
previewUrl,
|
||||
uploadAvatar,
|
||||
activeModel,
|
||||
endpoint,
|
||||
version,
|
||||
]);
|
||||
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
const file = event.target.files?.[0];
|
||||
|
|
@ -183,6 +203,8 @@ function Avatar({
|
|||
assistant_id,
|
||||
model: activeModel,
|
||||
formData,
|
||||
endpoint,
|
||||
version,
|
||||
});
|
||||
} else {
|
||||
showToast({
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useForm, FormProvider, Controller, useWatch } from 'react-hook-form';
|
||||
import { useGetModelsQuery, useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
||||
import { useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
Tools,
|
||||
QueryKeys,
|
||||
Capabilities,
|
||||
EModelEndpoint,
|
||||
actionDelimiter,
|
||||
ImageVisionTool,
|
||||
defaultAssistantFormValues,
|
||||
} from 'librechat-data-provider';
|
||||
import type { FunctionTool, TConfig, TPlugin } from 'librechat-data-provider';
|
||||
import type { AssistantForm, AssistantPanelProps } from '~/common';
|
||||
import type { FunctionTool, TPlugin, TEndpointsConfig } from 'librechat-data-provider';
|
||||
import { useCreateAssistantMutation, useUpdateAssistantMutation } from '~/data-provider';
|
||||
import { SelectDropDown, Checkbox, QuestionMark } from '~/components/ui';
|
||||
import { useAssistantsMapContext, useToastContext } from '~/Providers';
|
||||
import { useSelectAssistant, useLocalize } from '~/hooks';
|
||||
import { ToolSelectDialog } from '~/components/Tools';
|
||||
import CapabilitiesForm from './CapabilitiesForm';
|
||||
import { SelectDropDown } from '~/components/ui';
|
||||
import AssistantAvatar from './AssistantAvatar';
|
||||
import AssistantSelect from './AssistantSelect';
|
||||
import AssistantAction from './AssistantAction';
|
||||
|
|
@ -35,17 +35,20 @@ const inputClass =
|
|||
export default function AssistantPanel({
|
||||
// index = 0,
|
||||
setAction,
|
||||
endpoint,
|
||||
actions = [],
|
||||
setActivePanel,
|
||||
assistant_id: current_assistant_id,
|
||||
setCurrentAssistantId,
|
||||
}: AssistantPanelProps) {
|
||||
assistantsConfig,
|
||||
version,
|
||||
}: AssistantPanelProps & { assistantsConfig?: TConfig | null }) {
|
||||
const queryClient = useQueryClient();
|
||||
const modelsQuery = useGetModelsQuery();
|
||||
const assistantMap = useAssistantsMapContext();
|
||||
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
|
||||
|
||||
const allTools = queryClient.getQueryData<TPlugin[]>([QueryKeys.tools]) ?? [];
|
||||
const { onSelect: onSelectAssistant } = useSelectAssistant();
|
||||
const { onSelect: onSelectAssistant } = useSelectAssistant(endpoint);
|
||||
const { showToast } = useToastContext();
|
||||
const localize = useLocalize();
|
||||
|
||||
|
|
@ -55,44 +58,31 @@ export default function AssistantPanel({
|
|||
|
||||
const [showToolDialog, setShowToolDialog] = useState(false);
|
||||
|
||||
const { control, handleSubmit, reset, setValue, getValues } = methods;
|
||||
const { control, handleSubmit, reset } = methods;
|
||||
const assistant = useWatch({ control, name: 'assistant' });
|
||||
const functions = useWatch({ control, name: 'functions' });
|
||||
const assistant_id = useWatch({ control, name: 'id' });
|
||||
const model = useWatch({ control, name: 'model' });
|
||||
|
||||
const activeModel = useMemo(() => {
|
||||
return assistantMap?.[assistant_id]?.model;
|
||||
}, [assistantMap, assistant_id]);
|
||||
return assistantMap?.[endpoint]?.[assistant_id]?.model;
|
||||
}, [assistantMap, endpoint, assistant_id]);
|
||||
|
||||
const assistants = useMemo(() => endpointsConfig?.[EModelEndpoint.assistants], [endpointsConfig]);
|
||||
const retrievalModels = useMemo(() => new Set(assistants?.retrievalModels ?? []), [assistants]);
|
||||
const toolsEnabled = useMemo(
|
||||
() => assistants?.capabilities?.includes(Capabilities.tools),
|
||||
[assistants],
|
||||
() => assistantsConfig?.capabilities?.includes(Capabilities.tools),
|
||||
[assistantsConfig],
|
||||
);
|
||||
const actionsEnabled = useMemo(
|
||||
() => assistants?.capabilities?.includes(Capabilities.actions),
|
||||
[assistants],
|
||||
() => assistantsConfig?.capabilities?.includes(Capabilities.actions),
|
||||
[assistantsConfig],
|
||||
);
|
||||
const retrievalEnabled = useMemo(
|
||||
() => assistants?.capabilities?.includes(Capabilities.retrieval),
|
||||
[assistants],
|
||||
() => assistantsConfig?.capabilities?.includes(Capabilities.retrieval),
|
||||
[assistantsConfig],
|
||||
);
|
||||
const codeEnabled = useMemo(
|
||||
() => assistants?.capabilities?.includes(Capabilities.code_interpreter),
|
||||
[assistants],
|
||||
() => assistantsConfig?.capabilities?.includes(Capabilities.code_interpreter),
|
||||
[assistantsConfig],
|
||||
);
|
||||
const imageVisionEnabled = useMemo(
|
||||
() => assistants?.capabilities?.includes(Capabilities.image_vision),
|
||||
[assistants],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (model && !retrievalModels.has(model)) {
|
||||
setValue(Capabilities.retrieval, false);
|
||||
}
|
||||
}, [model, setValue, retrievalModels]);
|
||||
|
||||
/* Mutations */
|
||||
const update = useUpdateAssistantMutation({
|
||||
|
|
@ -145,7 +135,7 @@ export default function AssistantPanel({
|
|||
if (!functionName.includes(actionDelimiter)) {
|
||||
return functionName;
|
||||
} else {
|
||||
const assistant = assistantMap?.[assistant_id];
|
||||
const assistant = assistantMap?.[endpoint]?.[assistant_id];
|
||||
const tool = assistant?.tools?.find((tool) => tool.function?.name === functionName);
|
||||
if (assistant && tool) {
|
||||
return tool;
|
||||
|
|
@ -160,7 +150,7 @@ export default function AssistantPanel({
|
|||
tools.push({ type: Tools.code_interpreter });
|
||||
}
|
||||
if (data.retrieval) {
|
||||
tools.push({ type: Tools.retrieval });
|
||||
tools.push({ type: version == 2 ? Tools.file_search : Tools.retrieval });
|
||||
}
|
||||
if (data.image_vision) {
|
||||
tools.push(ImageVisionTool);
|
||||
|
|
@ -183,6 +173,7 @@ export default function AssistantPanel({
|
|||
instructions,
|
||||
model,
|
||||
tools,
|
||||
endpoint,
|
||||
},
|
||||
});
|
||||
return;
|
||||
|
|
@ -194,6 +185,8 @@ export default function AssistantPanel({
|
|||
instructions,
|
||||
model,
|
||||
tools,
|
||||
endpoint,
|
||||
version,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -211,6 +204,7 @@ export default function AssistantPanel({
|
|||
<AssistantSelect
|
||||
reset={reset}
|
||||
value={field.value}
|
||||
endpoint={endpoint}
|
||||
setCurrentAssistantId={setCurrentAssistantId}
|
||||
selectedAssistant={current_assistant_id ?? null}
|
||||
createMutation={create}
|
||||
|
|
@ -239,6 +233,8 @@ export default function AssistantPanel({
|
|||
createMutation={create}
|
||||
assistant_id={assistant_id ?? null}
|
||||
metadata={assistant?.['metadata'] ?? null}
|
||||
endpoint={endpoint}
|
||||
version={version}
|
||||
/>
|
||||
<label className={labelClass} htmlFor="name">
|
||||
{localize('com_ui_name')}
|
||||
|
|
@ -324,7 +320,7 @@ export default function AssistantPanel({
|
|||
emptyTitle={true}
|
||||
value={field.value}
|
||||
setValue={field.onChange}
|
||||
availableValues={modelsQuery.data?.[EModelEndpoint.assistants] ?? []}
|
||||
availableValues={modelsQuery.data?.[endpoint] ?? []}
|
||||
showAbove={false}
|
||||
showLabel={false}
|
||||
className={cn(
|
||||
|
|
@ -343,120 +339,17 @@ export default function AssistantPanel({
|
|||
/>
|
||||
</div>
|
||||
{/* Knowledge */}
|
||||
{(codeEnabled || retrievalEnabled) && (
|
||||
<Knowledge assistant_id={assistant_id} files={files} />
|
||||
{(codeEnabled || retrievalEnabled) && version == 1 && (
|
||||
<Knowledge assistant_id={assistant_id} files={files} endpoint={endpoint} />
|
||||
)}
|
||||
{/* Capabilities */}
|
||||
<div className="mb-6">
|
||||
<div className="mb-1.5 flex items-center">
|
||||
<span>
|
||||
<label className="text-token-text-primary block font-medium">
|
||||
{localize('com_assistants_capabilities')}
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
{codeEnabled && (
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
name={Capabilities.code_interpreter}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
|
||||
value={field?.value?.toString()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label text-token-text-primary w-full cursor-pointer"
|
||||
htmlFor={Capabilities.code_interpreter}
|
||||
onClick={() =>
|
||||
setValue(
|
||||
Capabilities.code_interpreter,
|
||||
!getValues(Capabilities.code_interpreter),
|
||||
{
|
||||
shouldDirty: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{localize('com_assistants_code_interpreter')}
|
||||
<QuestionMark />
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
{imageVisionEnabled && (
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
name={Capabilities.image_vision}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
|
||||
value={field?.value?.toString()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label text-token-text-primary w-full cursor-pointer"
|
||||
htmlFor={Capabilities.image_vision}
|
||||
onClick={() =>
|
||||
setValue(Capabilities.image_vision, !getValues(Capabilities.image_vision), {
|
||||
shouldDirty: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{localize('com_assistants_image_vision')}
|
||||
<QuestionMark />
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
{retrievalEnabled && (
|
||||
<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()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<CapabilitiesForm
|
||||
version={version}
|
||||
endpoint={endpoint}
|
||||
codeEnabled={codeEnabled}
|
||||
assistantsConfig={assistantsConfig}
|
||||
retrievalEnabled={retrievalEnabled}
|
||||
/>
|
||||
{/* Tools */}
|
||||
<div className="mb-6">
|
||||
<label className={labelClass}>
|
||||
|
|
@ -520,6 +413,7 @@ export default function AssistantPanel({
|
|||
activeModel={activeModel}
|
||||
setCurrentAssistantId={setCurrentAssistantId}
|
||||
createMutation={create}
|
||||
endpoint={endpoint}
|
||||
/>
|
||||
{/* Secondary Select Button */}
|
||||
{assistant_id && (
|
||||
|
|
@ -554,6 +448,7 @@ export default function AssistantPanel({
|
|||
isOpen={showToolDialog}
|
||||
setIsOpen={setShowToolDialog}
|
||||
assistant_id={assistant_id}
|
||||
endpoint={endpoint}
|
||||
/>
|
||||
</form>
|
||||
</FormProvider>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
import { Plus } from 'lucide-react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import {
|
||||
defaultAssistantFormValues,
|
||||
defaultOrderQuery,
|
||||
isImageVisionTool,
|
||||
EModelEndpoint,
|
||||
Capabilities,
|
||||
Tools,
|
||||
FileSources,
|
||||
Capabilities,
|
||||
EModelEndpoint,
|
||||
LocalStorageKeys,
|
||||
isImageVisionTool,
|
||||
defaultAssistantFormValues,
|
||||
} from 'librechat-data-provider';
|
||||
import type { UseFormReset } from 'react-hook-form';
|
||||
import type { UseMutationResult } from '@tanstack/react-query';
|
||||
import type { Assistant, AssistantCreateParams } from 'librechat-data-provider';
|
||||
import type { Assistant, AssistantCreateParams, AssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type {
|
||||
AssistantForm,
|
||||
Actions,
|
||||
TAssistantOption,
|
||||
ExtendedFile,
|
||||
AssistantForm,
|
||||
TAssistantOption,
|
||||
LastSelectedModels,
|
||||
} from '~/common';
|
||||
import SelectDropDown from '~/components/ui/SelectDropDown';
|
||||
|
|
@ -29,12 +30,14 @@ const keys = new Set(['name', 'id', 'description', 'instructions', 'model']);
|
|||
export default function AssistantSelect({
|
||||
reset,
|
||||
value,
|
||||
endpoint,
|
||||
selectedAssistant,
|
||||
setCurrentAssistantId,
|
||||
createMutation,
|
||||
}: {
|
||||
reset: UseFormReset<AssistantForm>;
|
||||
value: TAssistantOption;
|
||||
endpoint: AssistantsEndpoint;
|
||||
selectedAssistant: string | null;
|
||||
setCurrentAssistantId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
createMutation: UseMutationResult<Assistant, Error, AssistantCreateParams>;
|
||||
|
|
@ -43,42 +46,69 @@ export default function AssistantSelect({
|
|||
const fileMap = useFileMapContext();
|
||||
const lastSelectedAssistant = useRef<string | null>(null);
|
||||
const [lastSelectedModels] = useLocalStorage<LastSelectedModels>(
|
||||
'lastSelectedModel',
|
||||
LocalStorageKeys.LAST_MODEL,
|
||||
{} as LastSelectedModels,
|
||||
);
|
||||
|
||||
const assistants = useListAssistantsQuery(defaultOrderQuery, {
|
||||
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 ?? '',
|
||||
value: _assistant.id,
|
||||
files: _assistant?.file_ids ? ([] as Array<[string, ExtendedFile]>) : undefined,
|
||||
code_files: _assistant?.tool_resources?.code_interpreter?.file_ids
|
||||
? ([] as Array<[string, ExtendedFile]>)
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const handleFile = (file_id: string, list?: Array<[string, ExtendedFile]>) => {
|
||||
const file = fileMap?.[file_id];
|
||||
if (file) {
|
||||
list?.push([
|
||||
file_id,
|
||||
{
|
||||
file_id: file.file_id,
|
||||
type: file.type,
|
||||
filepath: file.filepath,
|
||||
filename: file.filename,
|
||||
width: file.width,
|
||||
height: file.height,
|
||||
size: file.bytes,
|
||||
preview: file.filepath,
|
||||
progress: 1,
|
||||
source,
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
list?.push([
|
||||
file_id,
|
||||
{
|
||||
file_id,
|
||||
type: '',
|
||||
filename: '',
|
||||
size: 1,
|
||||
progress: 1,
|
||||
filepath: endpoint,
|
||||
source,
|
||||
},
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
if (assistant.files && _assistant.file_ids) {
|
||||
_assistant.file_ids.forEach((file_id) => {
|
||||
const file = fileMap?.[file_id];
|
||||
if (file) {
|
||||
assistant.files?.push([
|
||||
file_id,
|
||||
{
|
||||
file_id: file.file_id,
|
||||
type: file.type,
|
||||
filepath: file.filepath,
|
||||
filename: file.filename,
|
||||
width: file.width,
|
||||
height: file.height,
|
||||
size: file.bytes,
|
||||
preview: file.filepath,
|
||||
progress: 1,
|
||||
source: FileSources.openai,
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
_assistant.file_ids.forEach((file_id) => handleFile(file_id, assistant.files));
|
||||
}
|
||||
|
||||
if (assistant.code_files && _assistant.tool_resources?.code_interpreter?.file_ids) {
|
||||
_assistant.tool_resources?.code_interpreter?.file_ids?.forEach((file_id) =>
|
||||
handleFile(file_id, assistant.code_files),
|
||||
);
|
||||
}
|
||||
|
||||
return assistant;
|
||||
}),
|
||||
});
|
||||
|
|
@ -92,7 +122,7 @@ export default function AssistantSelect({
|
|||
setCurrentAssistantId(undefined);
|
||||
return reset({
|
||||
...defaultAssistantFormValues,
|
||||
model: lastSelectedModels?.[EModelEndpoint.assistants] ?? '',
|
||||
model: lastSelectedModels?.[endpoint] ?? '',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -112,6 +142,9 @@ export default function AssistantSelect({
|
|||
?.filter((tool) => tool.type !== 'function' || isImageVisionTool(tool))
|
||||
?.map((tool) => tool?.function?.name || tool.type)
|
||||
.forEach((tool) => {
|
||||
if (tool === Tools.file_search) {
|
||||
actions[Capabilities.retrieval] = true;
|
||||
}
|
||||
actions[tool] = true;
|
||||
});
|
||||
|
||||
|
|
@ -141,7 +174,7 @@ export default function AssistantSelect({
|
|||
reset(formValues);
|
||||
setCurrentAssistantId(assistant?.id);
|
||||
},
|
||||
[assistants.data, reset, setCurrentAssistantId, createMutation, lastSelectedModels],
|
||||
[assistants.data, reset, setCurrentAssistantId, createMutation, endpoint, lastSelectedModels],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
51
client/src/components/SidePanel/Builder/CapabilitiesForm.tsx
Normal file
51
client/src/components/SidePanel/Builder/CapabilitiesForm.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { useMemo } from 'react';
|
||||
import { Capabilities } from 'librechat-data-provider';
|
||||
import type { TConfig, AssistantsEndpoint } from 'librechat-data-provider';
|
||||
import ImageVision from './ImageVision';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import Retrieval from './Retrieval';
|
||||
import Code from './Code';
|
||||
|
||||
export default function CapabilitiesForm({
|
||||
version,
|
||||
endpoint,
|
||||
codeEnabled,
|
||||
retrievalEnabled,
|
||||
assistantsConfig,
|
||||
}: {
|
||||
version: number | string;
|
||||
codeEnabled?: boolean;
|
||||
retrievalEnabled?: boolean;
|
||||
endpoint: AssistantsEndpoint;
|
||||
assistantsConfig?: TConfig | null;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
|
||||
const retrievalModels = useMemo(
|
||||
() => new Set(assistantsConfig?.retrievalModels ?? []),
|
||||
[assistantsConfig],
|
||||
);
|
||||
const imageVisionEnabled = useMemo(
|
||||
() => assistantsConfig?.capabilities?.includes(Capabilities.image_vision),
|
||||
[assistantsConfig],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mb-6">
|
||||
<div className="mb-1.5 flex items-center">
|
||||
<span>
|
||||
<label className="text-token-text-primary block font-medium">
|
||||
{localize('com_assistants_capabilities')}
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
{codeEnabled && <Code endpoint={endpoint} version={version} />}
|
||||
{imageVisionEnabled && version == 1 && <ImageVision />}
|
||||
{retrievalEnabled && (
|
||||
<Retrieval endpoint={endpoint} version={version} retrievalModels={retrievalModels} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
70
client/src/components/SidePanel/Builder/Code.tsx
Normal file
70
client/src/components/SidePanel/Builder/Code.tsx
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { useMemo } from 'react';
|
||||
import { Capabilities } from 'librechat-data-provider';
|
||||
import { useFormContext, Controller, useWatch } from 'react-hook-form';
|
||||
import type { AssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { AssistantForm } from '~/common';
|
||||
import { Checkbox, QuestionMark } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import CodeFiles from './CodeFiles';
|
||||
|
||||
export default function Code({
|
||||
version,
|
||||
endpoint,
|
||||
}: {
|
||||
version: number | string;
|
||||
endpoint: AssistantsEndpoint;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const methods = useFormContext<AssistantForm>();
|
||||
const { control, setValue, getValues } = methods;
|
||||
const assistant = useWatch({ control, name: 'assistant' });
|
||||
const assistant_id = useWatch({ control, name: 'id' });
|
||||
const files = useMemo(() => {
|
||||
if (typeof assistant === 'string') {
|
||||
return [];
|
||||
}
|
||||
return assistant.code_files;
|
||||
}, [assistant]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
name={Capabilities.code_interpreter}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
|
||||
value={field?.value?.toString()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label text-token-text-primary w-full cursor-pointer"
|
||||
htmlFor={Capabilities.code_interpreter}
|
||||
onClick={() =>
|
||||
setValue(Capabilities.code_interpreter, !getValues(Capabilities.code_interpreter), {
|
||||
shouldDirty: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="flex select-none items-center">
|
||||
{localize('com_assistants_code_interpreter')}
|
||||
<QuestionMark />
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{version == 2 && (
|
||||
<CodeFiles
|
||||
assistant_id={assistant_id}
|
||||
version={version}
|
||||
endpoint={endpoint}
|
||||
files={files}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
98
client/src/components/SidePanel/Builder/CodeFiles.tsx
Normal file
98
client/src/components/SidePanel/Builder/CodeFiles.tsx
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import { useState, useRef, useEffect } from 'react';
|
||||
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';
|
||||
import { useGetFileConfig } from '~/data-provider';
|
||||
import { useFileHandling } from '~/hooks/Files';
|
||||
import useLocalize from '~/hooks/useLocalize';
|
||||
import { useChatContext } from '~/Providers';
|
||||
|
||||
const tool_resource = EToolResources.code_interpreter;
|
||||
|
||||
export default function CodeFiles({
|
||||
endpoint,
|
||||
assistant_id,
|
||||
files: _files,
|
||||
}: {
|
||||
version: number | string;
|
||||
endpoint: AssistantsEndpoint;
|
||||
assistant_id: string;
|
||||
files?: [string, ExtendedFile][];
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { setFilesLoading } = useChatContext();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [files, setFiles] = useState<Map<string, ExtendedFile>>(new Map());
|
||||
const { data: fileConfig = defaultFileConfig } = useGetFileConfig({
|
||||
select: (data) => mergeFileConfig(data),
|
||||
});
|
||||
const { handleFileChange } = useFileHandling({
|
||||
overrideEndpoint: endpoint,
|
||||
additionalMetadata: { assistant_id, tool_resource },
|
||||
fileSetter: setFiles,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (_files) {
|
||||
setFiles(new Map(_files));
|
||||
}
|
||||
}, [_files]);
|
||||
|
||||
const endpointFileConfig = fileConfig.endpoints[endpoint];
|
||||
|
||||
if (endpointFileConfig?.disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleButtonClick = () => {
|
||||
// necessary to reset the input
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'mb-2'}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-token-text-tertiary rounded-lg text-xs">
|
||||
{localize('com_assistants_code_interpreter_files')}
|
||||
</div>
|
||||
<FileRow
|
||||
files={files}
|
||||
setFiles={setFiles}
|
||||
assistant_id={assistant_id}
|
||||
tool_resource={tool_resource}
|
||||
setFilesLoading={setFilesLoading}
|
||||
Wrapper={({ children }) => <div className="flex flex-wrap gap-2">{children}</div>}
|
||||
/>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!assistant_id}
|
||||
className="btn btn-neutral border-token-border-light relative h-8 rounded-lg font-medium"
|
||||
onClick={handleButtonClick}
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
<input
|
||||
multiple={true}
|
||||
type="file"
|
||||
style={{ display: 'none' }}
|
||||
tabIndex={-1}
|
||||
ref={fileInputRef}
|
||||
disabled={!assistant_id}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
{localize('com_ui_upload_files')}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,26 +1,29 @@
|
|||
import * as Popover from '@radix-ui/react-popover';
|
||||
import type { Assistant, AssistantCreateParams } from 'librechat-data-provider';
|
||||
import type { Assistant, AssistantCreateParams, AssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { UseMutationResult } from '@tanstack/react-query';
|
||||
import { Dialog, DialogTrigger, Label } from '~/components/ui';
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||
import { useChatContext, useToastContext } from '~/Providers';
|
||||
import { useDeleteAssistantMutation } from '~/data-provider';
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||
import { useLocalize, useSetIndexOptions } from '~/hooks';
|
||||
import { cn, removeFocusOutlines } from '~/utils/';
|
||||
import { NewTrashIcon } from '~/components/svg';
|
||||
import { useChatContext } from '~/Providers';
|
||||
|
||||
export default function ContextButton({
|
||||
activeModel,
|
||||
assistant_id,
|
||||
setCurrentAssistantId,
|
||||
createMutation,
|
||||
endpoint,
|
||||
}: {
|
||||
activeModel: string;
|
||||
assistant_id: string;
|
||||
setCurrentAssistantId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
createMutation: UseMutationResult<Assistant, Error, AssistantCreateParams>;
|
||||
endpoint: AssistantsEndpoint;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
const { conversation } = useChatContext();
|
||||
const { setOption } = useSetIndexOptions();
|
||||
|
||||
|
|
@ -31,6 +34,11 @@ export default function ContextButton({
|
|||
return;
|
||||
}
|
||||
|
||||
showToast({
|
||||
message: localize('com_ui_assistant_deleted'),
|
||||
status: 'success',
|
||||
});
|
||||
|
||||
if (createMutation.data?.id) {
|
||||
console.log('[deleteAssistant] resetting createMutation');
|
||||
createMutation.reset();
|
||||
|
|
@ -55,6 +63,13 @@ export default function ContextButton({
|
|||
|
||||
setCurrentAssistantId(firstAssistant.id);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
showToast({
|
||||
message: localize('com_ui_assistant_delete_error'),
|
||||
status: 'error',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (!assistant_id) {
|
||||
|
|
@ -138,7 +153,8 @@ export default function ContextButton({
|
|||
</>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: () => deleteAssistant.mutate({ assistant_id, model: activeModel }),
|
||||
selectHandler: () =>
|
||||
deleteAssistant.mutate({ assistant_id, model: activeModel, endpoint }),
|
||||
selectClasses: 'bg-red-600 hover:bg-red-700 dark:hover:bg-red-800 text-white',
|
||||
selectText: localize('com_ui_delete'),
|
||||
}}
|
||||
|
|
|
|||
43
client/src/components/SidePanel/Builder/ImageVision.tsx
Normal file
43
client/src/components/SidePanel/Builder/ImageVision.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { useFormContext, Controller } from 'react-hook-form';
|
||||
import { Capabilities } from 'librechat-data-provider';
|
||||
import type { AssistantForm } from '~/common';
|
||||
import { Checkbox, QuestionMark } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function ImageVision() {
|
||||
const localize = useLocalize();
|
||||
const methods = useFormContext<AssistantForm>();
|
||||
const { control, setValue, getValues } = methods;
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
name={Capabilities.image_vision}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
|
||||
value={field?.value?.toString()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label text-token-text-primary w-full cursor-pointer"
|
||||
htmlFor={Capabilities.image_vision}
|
||||
onClick={() =>
|
||||
setValue(Capabilities.image_vision, !getValues(Capabilities.image_vision), {
|
||||
shouldDirty: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{localize('com_assistants_image_vision')}
|
||||
<QuestionMark />
|
||||
</div>
|
||||
</label>
|
||||
</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"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useState, useRef, useEffect } from 'react';
|
||||
import {
|
||||
EModelEndpoint,
|
||||
mergeFileConfig,
|
||||
retrievalMimeTypes,
|
||||
fileConfig as defaultFileConfig,
|
||||
mergeFileConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import type { AssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { ExtendedFile } from '~/common';
|
||||
import FileRow from '~/components/Chat/Input/Files/FileRow';
|
||||
import { useGetFileConfig } from '~/data-provider';
|
||||
|
|
@ -26,9 +26,11 @@ const CodeInterpreterFiles = ({ children }: { children: React.ReactNode }) => {
|
|||
};
|
||||
|
||||
export default function Knowledge({
|
||||
endpoint,
|
||||
assistant_id,
|
||||
files: _files,
|
||||
}: {
|
||||
endpoint: AssistantsEndpoint;
|
||||
assistant_id: string;
|
||||
files?: [string, ExtendedFile][];
|
||||
}) {
|
||||
|
|
@ -40,7 +42,7 @@ export default function Knowledge({
|
|||
select: (data) => mergeFileConfig(data),
|
||||
});
|
||||
const { handleFileChange } = useFileHandling({
|
||||
overrideEndpoint: EModelEndpoint.assistants,
|
||||
overrideEndpoint: endpoint,
|
||||
additionalMetadata: { assistant_id },
|
||||
fileSetter: setFiles,
|
||||
});
|
||||
|
|
@ -51,7 +53,7 @@ export default function Knowledge({
|
|||
}
|
||||
}, [_files]);
|
||||
|
||||
const endpointFileConfig = fileConfig.endpoints[EModelEndpoint.assistants];
|
||||
const endpointFileConfig = fileConfig.endpoints[endpoint];
|
||||
|
||||
if (endpointFileConfig?.disabled) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import type { Action } from 'librechat-data-provider';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { defaultAssistantsVersion } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
||||
import type { Action, AssistantsEndpoint, TEndpointsConfig } from 'librechat-data-provider';
|
||||
import { useGetActionsQuery } from '~/data-provider';
|
||||
import AssistantPanel from './AssistantPanel';
|
||||
import { useChatContext } from '~/Providers';
|
||||
|
|
@ -9,11 +11,18 @@ import { Panel } from '~/common';
|
|||
export default function PanelSwitch() {
|
||||
const { conversation, index } = useChatContext();
|
||||
const [activePanel, setActivePanel] = useState(Panel.builder);
|
||||
const [action, setAction] = useState<Action | undefined>(undefined);
|
||||
const [currentAssistantId, setCurrentAssistantId] = useState<string | undefined>(
|
||||
conversation?.assistant_id,
|
||||
);
|
||||
const [action, setAction] = useState<Action | undefined>(undefined);
|
||||
const { data: actions = [] } = useGetActionsQuery();
|
||||
|
||||
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
|
||||
const { data: actions = [] } = useGetActionsQuery(conversation?.endpoint as AssistantsEndpoint);
|
||||
|
||||
const assistantsConfig = useMemo(
|
||||
() => endpointsConfig?.[conversation?.endpoint ?? ''],
|
||||
[conversation?.endpoint, endpointsConfig],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (conversation?.assistant_id) {
|
||||
|
|
@ -21,6 +30,12 @@ export default function PanelSwitch() {
|
|||
}
|
||||
}, [conversation?.assistant_id]);
|
||||
|
||||
if (!conversation?.endpoint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const version = assistantsConfig?.version ?? defaultAssistantsVersion[conversation.endpoint];
|
||||
|
||||
if (activePanel === Panel.actions || action) {
|
||||
return (
|
||||
<ActionsPanel
|
||||
|
|
@ -32,6 +47,8 @@ export default function PanelSwitch() {
|
|||
setActivePanel={setActivePanel}
|
||||
assistant_id={currentAssistantId}
|
||||
setCurrentAssistantId={setCurrentAssistantId}
|
||||
endpoint={conversation.endpoint as AssistantsEndpoint}
|
||||
version={version}
|
||||
/>
|
||||
);
|
||||
} else if (activePanel === Panel.builder) {
|
||||
|
|
@ -45,6 +62,9 @@ export default function PanelSwitch() {
|
|||
setActivePanel={setActivePanel}
|
||||
assistant_id={currentAssistantId}
|
||||
setCurrentAssistantId={setCurrentAssistantId}
|
||||
endpoint={conversation.endpoint as AssistantsEndpoint}
|
||||
assistantsConfig={assistantsConfig}
|
||||
version={version}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
94
client/src/components/SidePanel/Builder/Retrieval.tsx
Normal file
94
client/src/components/SidePanel/Builder/Retrieval.tsx
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import { useEffect, useMemo } from 'react';
|
||||
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 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({
|
||||
version,
|
||||
retrievalModels,
|
||||
}: {
|
||||
version: number | string;
|
||||
retrievalModels: Set<string>;
|
||||
endpoint: AssistantsEndpoint;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const methods = useFormContext<AssistantForm>();
|
||||
const { control, setValue, getValues } = methods;
|
||||
const model = useWatch({ control, name: 'model' });
|
||||
const assistant = useWatch({ control, name: 'assistant' });
|
||||
|
||||
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 && isDisabled) {
|
||||
setValue(Capabilities.retrieval, false);
|
||||
}
|
||||
}, [model, setValue, isDisabled]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<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}
|
||||
/>
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import throttle from 'lodash/throttle';
|
||||
import { EModelEndpoint, getConfigDefaults } from 'librechat-data-provider';
|
||||
import { getConfigDefaults } from 'librechat-data-provider';
|
||||
import { useState, useRef, useCallback, useEffect, useMemo, memo } from 'react';
|
||||
import {
|
||||
useGetEndpointsQuery,
|
||||
|
|
@ -61,7 +61,7 @@ const SidePanel = ({
|
|||
return activePanel ? activePanel : undefined;
|
||||
}, []);
|
||||
|
||||
const assistants = useMemo(() => endpointsConfig?.[EModelEndpoint.assistants], [endpointsConfig]);
|
||||
const assistants = useMemo(() => endpointsConfig?.[endpoint ?? ''], [endpoint, endpointsConfig]);
|
||||
const userProvidesKey = useMemo(
|
||||
() => !!endpointsConfig?.[endpoint ?? '']?.userProvide,
|
||||
[endpointsConfig, endpoint],
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { SwitcherProps } from '~/common';
|
||||
import { Separator } from '~/components/ui/Separator';
|
||||
import AssistantSwitcher from './AssistantSwitcher';
|
||||
import ModelSwitcher from './ModelSwitcher';
|
||||
|
||||
export default function Switcher(props: SwitcherProps) {
|
||||
if (props.endpoint === EModelEndpoint.assistants && props.endpointKeyProvided) {
|
||||
if (isAssistantsEndpoint(props.endpoint) && props.endpointKeyProvided) {
|
||||
return (
|
||||
<>
|
||||
<AssistantSwitcher {...props} />
|
||||
<Separator className="bg-gray-100/50 dark:bg-gray-600" />
|
||||
</>
|
||||
);
|
||||
} else if (props.endpoint === EModelEndpoint.assistants) {
|
||||
} else if (isAssistantsEndpoint(props.endpoint)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Search, X } from 'lucide-react';
|
|||
import { Dialog } from '@headlessui/react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||
import type { TError, TPluginAction } from 'librechat-data-provider';
|
||||
import type { AssistantsEndpoint, TError, TPluginAction } from 'librechat-data-provider';
|
||||
import type { TPluginStoreDialogProps } from '~/common/types';
|
||||
import { PluginPagination, PluginAuthForm } from '~/components/Plugins/Store';
|
||||
import { useLocalize, usePluginDialogHelpers } from '~/hooks';
|
||||
|
|
@ -13,10 +13,11 @@ import ToolItem from './ToolItem';
|
|||
function ToolSelectDialog({
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
}: TPluginStoreDialogProps & { assistant_id?: string }) {
|
||||
endpoint,
|
||||
}: TPluginStoreDialogProps & { assistant_id?: string; endpoint: AssistantsEndpoint }) {
|
||||
const localize = useLocalize();
|
||||
const { getValues, setValue } = useFormContext();
|
||||
const { data: tools = [] } = useAvailableToolsQuery();
|
||||
const { data: tools = [] } = useAvailableToolsQuery(endpoint);
|
||||
|
||||
const {
|
||||
maxPage,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import { LocalStorageKeys } from 'librechat-data-provider';
|
||||
import {
|
||||
EToolResources,
|
||||
LocalStorageKeys,
|
||||
defaultAssistantsVersion,
|
||||
} from 'librechat-data-provider';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import type { UseMutationResult } from '@tanstack/react-query';
|
||||
import type t from 'librechat-data-provider';
|
||||
|
|
@ -376,9 +380,10 @@ export const useUploadFileMutation = (
|
|||
const { onSuccess, ...options } = _options || {};
|
||||
return useMutation([MutationKeys.fileUpload], {
|
||||
mutationFn: (body: FormData) => {
|
||||
const height = body.get('height');
|
||||
const width = body.get('width');
|
||||
if (height && width) {
|
||||
const height = body.get('height');
|
||||
const version = body.get('version') as number | string;
|
||||
if (height && width && (!version || version != 2)) {
|
||||
return dataService.uploadImage(body);
|
||||
}
|
||||
|
||||
|
|
@ -391,8 +396,10 @@ export const useUploadFileMutation = (
|
|||
...(_files ?? []),
|
||||
]);
|
||||
|
||||
const endpoint = formData.get('endpoint');
|
||||
const assistant_id = formData.get('assistant_id');
|
||||
const message_file = formData.get('message_file');
|
||||
const tool_resource = formData.get('tool_resource');
|
||||
|
||||
if (!assistant_id || message_file === 'true') {
|
||||
onSuccess?.(data, formData, context);
|
||||
|
|
@ -400,7 +407,7 @@ export const useUploadFileMutation = (
|
|||
}
|
||||
|
||||
queryClient.setQueryData<t.AssistantListResponse>(
|
||||
[QueryKeys.assistants, defaultOrderQuery],
|
||||
[QueryKeys.assistants, endpoint, defaultOrderQuery],
|
||||
(prev) => {
|
||||
if (!prev) {
|
||||
return prev;
|
||||
|
|
@ -409,13 +416,29 @@ export const useUploadFileMutation = (
|
|||
return {
|
||||
...prev,
|
||||
data: prev?.data.map((assistant) => {
|
||||
if (assistant.id === assistant_id) {
|
||||
return {
|
||||
...assistant,
|
||||
file_ids: [...assistant.file_ids, data.file_id],
|
||||
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 as string] ?? {
|
||||
file_ids: [],
|
||||
};
|
||||
prevResource.file_ids.push(data.file_id);
|
||||
update['tool_resources'] = {
|
||||
...prevResources,
|
||||
[tool_resource as string]: prevResource,
|
||||
};
|
||||
}
|
||||
return assistant;
|
||||
return {
|
||||
...assistant,
|
||||
...update,
|
||||
};
|
||||
}),
|
||||
};
|
||||
},
|
||||
|
|
@ -436,7 +459,8 @@ export const useDeleteFilesMutation = (
|
|||
const queryClient = useQueryClient();
|
||||
const { onSuccess, ...options } = _options || {};
|
||||
return useMutation([MutationKeys.fileDelete], {
|
||||
mutationFn: (body: t.DeleteFilesBody) => dataService.deleteFiles(body.files, body.assistant_id),
|
||||
mutationFn: (body: t.DeleteFilesBody) =>
|
||||
dataService.deleteFiles(body.files, body.assistant_id, body.tool_resource),
|
||||
...(options || {}),
|
||||
onSuccess: (data, ...args) => {
|
||||
queryClient.setQueryData<t.TFile[] | undefined>([QueryKeys.files], (cachefiles) => {
|
||||
|
|
@ -542,6 +566,7 @@ export const useCreateAssistantMutation = (
|
|||
onSuccess: (newAssistant, variables, context) => {
|
||||
const listRes = queryClient.getQueryData<t.AssistantListResponse>([
|
||||
QueryKeys.assistants,
|
||||
variables.endpoint,
|
||||
defaultOrderQuery,
|
||||
]);
|
||||
|
||||
|
|
@ -552,7 +577,7 @@ export const useCreateAssistantMutation = (
|
|||
const currentAssistants = [newAssistant, ...JSON.parse(JSON.stringify(listRes.data))];
|
||||
|
||||
queryClient.setQueryData<t.AssistantListResponse>(
|
||||
[QueryKeys.assistants, defaultOrderQuery],
|
||||
[QueryKeys.assistants, variables.endpoint, defaultOrderQuery],
|
||||
{
|
||||
...listRes,
|
||||
data: currentAssistants,
|
||||
|
|
@ -576,14 +601,23 @@ export const useUpdateAssistantMutation = (
|
|||
> => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
({ assistant_id, data }: { assistant_id: string; data: t.AssistantUpdateParams }) =>
|
||||
dataService.updateAssistant(assistant_id, data),
|
||||
({ assistant_id, data }: { assistant_id: string; data: t.AssistantUpdateParams }) => {
|
||||
const { endpoint } = data;
|
||||
const endpointsConfig = queryClient.getQueryData<t.TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
return dataService.updateAssistant({
|
||||
data,
|
||||
version,
|
||||
assistant_id,
|
||||
});
|
||||
},
|
||||
{
|
||||
onMutate: (variables) => options?.onMutate?.(variables),
|
||||
onError: (error, variables, context) => options?.onError?.(error, variables, context),
|
||||
onSuccess: (updatedAssistant, variables, context) => {
|
||||
const listRes = queryClient.getQueryData<t.AssistantListResponse>([
|
||||
QueryKeys.assistants,
|
||||
variables.data.endpoint,
|
||||
defaultOrderQuery,
|
||||
]);
|
||||
|
||||
|
|
@ -592,7 +626,7 @@ export const useUpdateAssistantMutation = (
|
|||
}
|
||||
|
||||
queryClient.setQueryData<t.AssistantListResponse>(
|
||||
[QueryKeys.assistants, defaultOrderQuery],
|
||||
[QueryKeys.assistants, variables.data.endpoint, defaultOrderQuery],
|
||||
{
|
||||
...listRes,
|
||||
data: listRes.data.map((assistant) => {
|
||||
|
|
@ -617,14 +651,18 @@ export const useDeleteAssistantMutation = (
|
|||
): UseMutationResult<void, Error, t.DeleteAssistantBody> => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
({ assistant_id, model }: t.DeleteAssistantBody) =>
|
||||
dataService.deleteAssistant(assistant_id, model),
|
||||
({ assistant_id, model, endpoint }: t.DeleteAssistantBody) => {
|
||||
const endpointsConfig = queryClient.getQueryData<t.TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
return dataService.deleteAssistant({ assistant_id, model, version, endpoint });
|
||||
},
|
||||
{
|
||||
onMutate: (variables) => options?.onMutate?.(variables),
|
||||
onError: (error, variables, context) => options?.onError?.(error, variables, context),
|
||||
onSuccess: (_data, variables, context) => {
|
||||
const listRes = queryClient.getQueryData<t.AssistantListResponse>([
|
||||
QueryKeys.assistants,
|
||||
variables.endpoint,
|
||||
defaultOrderQuery,
|
||||
]);
|
||||
|
||||
|
|
@ -635,7 +673,7 @@ export const useDeleteAssistantMutation = (
|
|||
const data = listRes.data.filter((assistant) => assistant.id !== variables.assistant_id);
|
||||
|
||||
queryClient.setQueryData<t.AssistantListResponse>(
|
||||
[QueryKeys.assistants, defaultOrderQuery],
|
||||
[QueryKeys.assistants, variables.endpoint, defaultOrderQuery],
|
||||
{
|
||||
...listRes,
|
||||
data,
|
||||
|
|
@ -687,6 +725,7 @@ export const useUpdateAction = (
|
|||
onSuccess: (updateActionResponse, variables, context) => {
|
||||
const listRes = queryClient.getQueryData<t.AssistantListResponse>([
|
||||
QueryKeys.assistants,
|
||||
variables.endpoint,
|
||||
defaultOrderQuery,
|
||||
]);
|
||||
|
||||
|
|
@ -696,15 +735,18 @@ export const useUpdateAction = (
|
|||
|
||||
const updatedAssistant = updateActionResponse[1];
|
||||
|
||||
queryClient.setQueryData<t.AssistantListResponse>([QueryKeys.assistants, defaultOrderQuery], {
|
||||
...listRes,
|
||||
data: listRes.data.map((assistant) => {
|
||||
if (assistant.id === variables.assistant_id) {
|
||||
return updatedAssistant;
|
||||
}
|
||||
return assistant;
|
||||
}),
|
||||
});
|
||||
queryClient.setQueryData<t.AssistantListResponse>(
|
||||
[QueryKeys.assistants, variables.endpoint, defaultOrderQuery],
|
||||
{
|
||||
...listRes,
|
||||
data: listRes.data.map((assistant) => {
|
||||
if (assistant.id === variables.assistant_id) {
|
||||
return updatedAssistant;
|
||||
}
|
||||
return assistant;
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
queryClient.setQueryData<t.Action[]>([QueryKeys.actions], (prev) => {
|
||||
return prev
|
||||
|
|
@ -735,8 +777,15 @@ export const useDeleteAction = (
|
|||
> => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation([MutationKeys.deleteAction], {
|
||||
mutationFn: (variables: t.DeleteActionVariables) =>
|
||||
dataService.deleteAction(variables.assistant_id, variables.action_id, variables.model),
|
||||
mutationFn: (variables: t.DeleteActionVariables) => {
|
||||
const { endpoint } = variables;
|
||||
const endpointsConfig = queryClient.getQueryData<t.TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
return dataService.deleteAction({
|
||||
...variables,
|
||||
version,
|
||||
});
|
||||
},
|
||||
|
||||
onMutate: (variables) => options?.onMutate?.(variables),
|
||||
onError: (error, variables, context) => options?.onError?.(error, variables, context),
|
||||
|
|
@ -750,7 +799,7 @@ export const useDeleteAction = (
|
|||
});
|
||||
|
||||
queryClient.setQueryData<t.AssistantListResponse>(
|
||||
[QueryKeys.assistants, defaultOrderQuery],
|
||||
[QueryKeys.assistants, variables.endpoint, defaultOrderQuery],
|
||||
(prev) => {
|
||||
if (!prev) {
|
||||
return prev;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import { EModelEndpoint, QueryKeys, dataService, defaultOrderQuery } from 'librechat-data-provider';
|
||||
import {
|
||||
QueryKeys,
|
||||
dataService,
|
||||
defaultOrderQuery,
|
||||
defaultAssistantsVersion,
|
||||
} from 'librechat-data-provider';
|
||||
import { useQuery, useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import type {
|
||||
UseInfiniteQueryOptions,
|
||||
|
|
@ -194,43 +199,46 @@ export const useSharedLinksInfiniteQuery = (
|
|||
/**
|
||||
* Hook for getting all available tools for Assistants
|
||||
*/
|
||||
export const useAvailableToolsQuery = (): QueryObserverResult<TPlugin[]> => {
|
||||
export const useAvailableToolsQuery = (
|
||||
endpoint: t.AssistantsEndpoint,
|
||||
): QueryObserverResult<TPlugin[]> => {
|
||||
const queryClient = useQueryClient();
|
||||
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([
|
||||
QueryKeys.name,
|
||||
EModelEndpoint.assistants,
|
||||
]);
|
||||
const userProvidesKey = !!endpointsConfig?.[EModelEndpoint.assistants]?.userProvide;
|
||||
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([QueryKeys.name, endpoint]);
|
||||
const userProvidesKey = !!endpointsConfig?.[endpoint]?.userProvide;
|
||||
const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true;
|
||||
const enabled = !!endpointsConfig?.[EModelEndpoint.assistants] && keyProvided;
|
||||
return useQuery<TPlugin[]>([QueryKeys.tools], () => dataService.getAvailableTools(), {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
enabled,
|
||||
});
|
||||
const enabled = !!endpointsConfig?.[endpoint] && keyProvided;
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
return useQuery<TPlugin[]>(
|
||||
[QueryKeys.tools],
|
||||
() => dataService.getAvailableTools(version, endpoint),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
enabled,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for listing all assistants, with optional parameters provided for pagination and sorting
|
||||
*/
|
||||
export const useListAssistantsQuery = <TData = AssistantListResponse>(
|
||||
params: AssistantListParams = defaultOrderQuery,
|
||||
endpoint: t.AssistantsEndpoint,
|
||||
params: Omit<AssistantListParams, 'endpoint'> = defaultOrderQuery,
|
||||
config?: UseQueryOptions<AssistantListResponse, unknown, TData>,
|
||||
): QueryObserverResult<TData> => {
|
||||
const queryClient = useQueryClient();
|
||||
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([
|
||||
QueryKeys.name,
|
||||
EModelEndpoint.assistants,
|
||||
]);
|
||||
const userProvidesKey = !!endpointsConfig?.[EModelEndpoint.assistants]?.userProvide;
|
||||
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([QueryKeys.name, endpoint]);
|
||||
const userProvidesKey = !!endpointsConfig?.[endpoint]?.userProvide;
|
||||
const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true;
|
||||
const enabled = !!endpointsConfig?.[EModelEndpoint.assistants] && keyProvided;
|
||||
const enabled = !!endpointsConfig?.[endpoint] && keyProvided;
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
return useQuery<AssistantListResponse, unknown, TData>(
|
||||
[QueryKeys.assistants, params],
|
||||
() => dataService.listAssistants(params),
|
||||
[QueryKeys.assistants, endpoint, params],
|
||||
() => dataService.listAssistants({ ...params, endpoint }, version),
|
||||
{
|
||||
// Example selector to sort them by created_at
|
||||
// select: (res) => {
|
||||
|
|
@ -246,6 +254,7 @@ export const useListAssistantsQuery = <TData = AssistantListResponse>(
|
|||
);
|
||||
};
|
||||
|
||||
/*
|
||||
export const useListAssistantsInfiniteQuery = (
|
||||
params?: AssistantListParams,
|
||||
config?: UseInfiniteQueryOptions<AssistantListResponse, Error>,
|
||||
|
|
@ -275,26 +284,31 @@ export const useListAssistantsInfiniteQuery = (
|
|||
},
|
||||
);
|
||||
};
|
||||
*/
|
||||
|
||||
/**
|
||||
* Hook for retrieving details about a single assistant
|
||||
*/
|
||||
export const useGetAssistantByIdQuery = (
|
||||
endpoint: t.AssistantsEndpoint,
|
||||
assistant_id: string,
|
||||
config?: UseQueryOptions<Assistant>,
|
||||
): QueryObserverResult<Assistant> => {
|
||||
const queryClient = useQueryClient();
|
||||
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([
|
||||
QueryKeys.name,
|
||||
EModelEndpoint.assistants,
|
||||
]);
|
||||
const userProvidesKey = !!endpointsConfig?.[EModelEndpoint.assistants]?.userProvide;
|
||||
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([QueryKeys.name, endpoint]);
|
||||
const userProvidesKey = !!endpointsConfig?.[endpoint]?.userProvide;
|
||||
const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true;
|
||||
const enabled = !!endpointsConfig?.[EModelEndpoint.assistants] && keyProvided;
|
||||
const enabled = !!endpointsConfig?.[endpoint] && keyProvided;
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
return useQuery<Assistant>(
|
||||
[QueryKeys.assistant, assistant_id],
|
||||
() => dataService.getAssistantById(assistant_id),
|
||||
() =>
|
||||
dataService.getAssistantById({
|
||||
endpoint,
|
||||
assistant_id,
|
||||
version,
|
||||
}),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
|
|
@ -311,43 +325,53 @@ export const useGetAssistantByIdQuery = (
|
|||
* Hook for retrieving user's saved Assistant Actions
|
||||
*/
|
||||
export const useGetActionsQuery = <TData = Action[]>(
|
||||
endpoint: t.AssistantsEndpoint,
|
||||
config?: UseQueryOptions<Action[], unknown, TData>,
|
||||
): QueryObserverResult<TData> => {
|
||||
const queryClient = useQueryClient();
|
||||
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([
|
||||
QueryKeys.name,
|
||||
EModelEndpoint.assistants,
|
||||
]);
|
||||
const userProvidesKey = !!endpointsConfig?.[EModelEndpoint.assistants]?.userProvide;
|
||||
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([QueryKeys.name, endpoint]);
|
||||
const userProvidesKey = !!endpointsConfig?.[endpoint]?.userProvide;
|
||||
const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true;
|
||||
const enabled = !!endpointsConfig?.[EModelEndpoint.assistants] && keyProvided;
|
||||
return useQuery<Action[], unknown, TData>([QueryKeys.actions], () => dataService.getActions(), {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
...config,
|
||||
enabled: config?.enabled !== undefined ? config?.enabled && enabled : enabled,
|
||||
});
|
||||
const enabled = !!endpointsConfig?.[endpoint] && keyProvided;
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
return useQuery<Action[], unknown, TData>(
|
||||
[QueryKeys.actions],
|
||||
() =>
|
||||
dataService.getActions({
|
||||
endpoint,
|
||||
version,
|
||||
}),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
...config,
|
||||
enabled: config?.enabled !== undefined ? config?.enabled && enabled : enabled,
|
||||
},
|
||||
);
|
||||
};
|
||||
/**
|
||||
* Hook for retrieving user's saved Assistant Documents (metadata saved to Database)
|
||||
*/
|
||||
export const useGetAssistantDocsQuery = (
|
||||
endpoint: t.AssistantsEndpoint,
|
||||
config?: UseQueryOptions<AssistantDocument[]>,
|
||||
): QueryObserverResult<AssistantDocument[], unknown> => {
|
||||
const queryClient = useQueryClient();
|
||||
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([
|
||||
QueryKeys.name,
|
||||
EModelEndpoint.assistants,
|
||||
]);
|
||||
const userProvidesKey = !!endpointsConfig?.[EModelEndpoint.assistants]?.userProvide;
|
||||
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([QueryKeys.name, endpoint]);
|
||||
const userProvidesKey = !!endpointsConfig?.[endpoint]?.userProvide;
|
||||
const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true;
|
||||
const enabled = !!endpointsConfig?.[EModelEndpoint.assistants] && keyProvided;
|
||||
const enabled = !!endpointsConfig?.[endpoint] && keyProvided;
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
return useQuery<AssistantDocument[]>(
|
||||
[QueryKeys.assistantDocs],
|
||||
() => dataService.getAssistantDocs(),
|
||||
() =>
|
||||
dataService.getAssistantDocs({
|
||||
endpoint,
|
||||
version,
|
||||
}),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export { default as useAssistantsMap } from './useAssistantsMap';
|
||||
export { default as useSelectAssistant } from './useSelectAssistant';
|
||||
export { default as useAssistantListMap } from './useAssistantListMap';
|
||||
|
|
|
|||
44
client/src/hooks/Assistants/useAssistantListMap.ts
Normal file
44
client/src/hooks/Assistants/useAssistantListMap.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { useMemo } from 'react';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { AssistantListResponse, AssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { AssistantListItem } from '~/common';
|
||||
import { useListAssistantsQuery } from '~/data-provider';
|
||||
|
||||
const selectAssistantsResponse = (res: AssistantListResponse): AssistantListItem[] =>
|
||||
res.data.map(({ id, name, metadata, model }) => ({
|
||||
id,
|
||||
name: name ?? '',
|
||||
metadata,
|
||||
model,
|
||||
}));
|
||||
|
||||
export default function useAssistantListMap<T = AssistantListItem[] | null>(
|
||||
selector: (res: AssistantListResponse) => T = selectAssistantsResponse as (
|
||||
res: AssistantListResponse,
|
||||
) => T,
|
||||
): Record<AssistantsEndpoint, T> {
|
||||
const { data: assistantsList = null } = useListAssistantsQuery(
|
||||
EModelEndpoint.assistants,
|
||||
undefined,
|
||||
{
|
||||
select: selector,
|
||||
},
|
||||
);
|
||||
|
||||
const { data: azureAssistants = null } = useListAssistantsQuery(
|
||||
EModelEndpoint.azureAssistants,
|
||||
undefined,
|
||||
{
|
||||
select: selector,
|
||||
},
|
||||
);
|
||||
|
||||
const assistantListMap = useMemo(() => {
|
||||
return {
|
||||
[EModelEndpoint.assistants]: assistantsList as T,
|
||||
[EModelEndpoint.azureAssistants]: azureAssistants as T,
|
||||
};
|
||||
}, [assistantsList, azureAssistants]);
|
||||
|
||||
return assistantListMap;
|
||||
}
|
||||
|
|
@ -1,12 +1,28 @@
|
|||
import { defaultOrderQuery } from 'librechat-data-provider';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { TAssistantsMap } from 'librechat-data-provider';
|
||||
import { useListAssistantsQuery } from '~/data-provider';
|
||||
import { mapAssistants } from '~/utils';
|
||||
|
||||
export default function useAssistantsMap({ isAuthenticated }: { isAuthenticated: boolean }) {
|
||||
const { data: assistantMap = {} } = useListAssistantsQuery(defaultOrderQuery, {
|
||||
export default function useAssistantsMap({
|
||||
isAuthenticated,
|
||||
}: {
|
||||
isAuthenticated: boolean;
|
||||
}): TAssistantsMap {
|
||||
const { data: assistants = {} } = useListAssistantsQuery(EModelEndpoint.assistants, undefined, {
|
||||
select: (res) => mapAssistants(res.data),
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
const { data: azureAssistants = {} } = useListAssistantsQuery(
|
||||
EModelEndpoint.azureAssistants,
|
||||
undefined,
|
||||
{
|
||||
select: (res) => mapAssistants(res.data),
|
||||
enabled: isAuthenticated,
|
||||
},
|
||||
);
|
||||
|
||||
return assistantMap;
|
||||
return {
|
||||
[EModelEndpoint.assistants]: assistants,
|
||||
[EModelEndpoint.azureAssistants]: azureAssistants,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,30 @@
|
|||
import { useCallback } from 'react';
|
||||
import { EModelEndpoint, defaultOrderQuery } from 'librechat-data-provider';
|
||||
import type { TConversation, TPreset } from 'librechat-data-provider';
|
||||
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { AssistantsEndpoint, TConversation, TPreset } from 'librechat-data-provider';
|
||||
import useDefaultConvo from '~/hooks/Conversations/useDefaultConvo';
|
||||
import { useListAssistantsQuery } from '~/data-provider';
|
||||
import { useChatContext } from '~/Providers/ChatContext';
|
||||
import useAssistantListMap from './useAssistantListMap';
|
||||
import { mapAssistants } from '~/utils';
|
||||
|
||||
export default function useSelectAssistant() {
|
||||
export default function useSelectAssistant(endpoint: AssistantsEndpoint) {
|
||||
const getDefaultConversation = useDefaultConvo();
|
||||
const { conversation, newConversation } = useChatContext();
|
||||
const { data: assistantMap = {} } = useListAssistantsQuery(defaultOrderQuery, {
|
||||
select: (res) => mapAssistants(res.data),
|
||||
});
|
||||
const assistantMap = useAssistantListMap((res) => mapAssistants(res.data));
|
||||
|
||||
const onSelect = useCallback(
|
||||
(value: string) => {
|
||||
const assistant = assistantMap?.[value];
|
||||
const assistant = assistantMap?.[endpoint]?.[value];
|
||||
if (!assistant) {
|
||||
return;
|
||||
}
|
||||
const template: Partial<TPreset | TConversation> = {
|
||||
endpoint: EModelEndpoint.assistants,
|
||||
endpoint,
|
||||
assistant_id: assistant.id,
|
||||
model: assistant.model,
|
||||
conversationId: 'new',
|
||||
};
|
||||
|
||||
if (conversation?.endpoint === EModelEndpoint.assistants) {
|
||||
if (isAssistantsEndpoint(conversation?.endpoint)) {
|
||||
const currentConvo = getDefaultConversation({
|
||||
conversation: { ...(conversation ?? {}) },
|
||||
preset: template,
|
||||
|
|
@ -44,7 +42,7 @@ export default function useSelectAssistant() {
|
|||
preset: template as Partial<TPreset>,
|
||||
});
|
||||
},
|
||||
[assistantMap, conversation, getDefaultConversation, newConversation],
|
||||
[endpoint, assistantMap, conversation, getDefaultConversation, newConversation],
|
||||
);
|
||||
|
||||
return { onSelect };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useSetRecoilState, useResetRecoilState, useRecoilCallback } from 'recoil';
|
||||
import { useGetEndpointsQuery, useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import type {
|
||||
|
|
@ -10,11 +11,10 @@ import type {
|
|||
TEndpointsConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import { buildDefaultConvo, getDefaultEndpoint, getEndpointField } from '~/utils';
|
||||
import useOriginNavigate from '../useOriginNavigate';
|
||||
import store from '~/store';
|
||||
|
||||
const useConversation = () => {
|
||||
const navigate = useOriginNavigate();
|
||||
const navigate = useNavigate();
|
||||
const setConversation = useSetRecoilState(store.conversation);
|
||||
const resetLatestMessage = useResetRecoilState(store.latestMessage);
|
||||
const setMessages = useSetRecoilState<TMessagesAtom>(store.messages);
|
||||
|
|
@ -59,7 +59,7 @@ const useConversation = () => {
|
|||
resetLatestMessage();
|
||||
|
||||
if (conversation.conversationId === 'new' && !modelsData) {
|
||||
navigate('new');
|
||||
navigate('/c/new');
|
||||
}
|
||||
},
|
||||
[endpointsConfig, modelsQuery.data],
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useSetRecoilState, useResetRecoilState } from 'recoil';
|
||||
import { QueryKeys, EModelEndpoint, LocalStorageKeys } from 'librechat-data-provider';
|
||||
import type { TConversation, TEndpointsConfig, TModelsConfig } from 'librechat-data-provider';
|
||||
import { buildDefaultConvo, getDefaultEndpoint, getEndpointField } from '~/utils';
|
||||
import useOriginNavigate from '../useOriginNavigate';
|
||||
import store from '~/store';
|
||||
|
||||
const useNavigateToConvo = (index = 0) => {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useOriginNavigate();
|
||||
const { setConversation } = store.useCreateConversationAtom(index);
|
||||
const setSubmission = useSetRecoilState(store.submissionByIndex(index));
|
||||
const resetLatestMessage = useResetRecoilState(store.latestMessageFamily(index));
|
||||
|
|
@ -48,7 +48,7 @@ const useNavigateToConvo = (index = 0) => {
|
|||
});
|
||||
}
|
||||
setConversation(convo);
|
||||
navigate(convo?.conversationId);
|
||||
navigate(`/c/${convo.conversationId ?? 'new'}`);
|
||||
};
|
||||
|
||||
const navigateWithLastTools = (conversation: TConversation) => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import exportFromJSON from 'export-from-json';
|
|||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useRecoilState, useSetRecoilState, useRecoilValue } from 'recoil';
|
||||
import { QueryKeys, modularEndpoints, EModelEndpoint } from 'librechat-data-provider';
|
||||
import { QueryKeys, modularEndpoints, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import { useCreatePresetMutation, useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import type { TPreset, TEndpointsConfig } from 'librechat-data-provider';
|
||||
import {
|
||||
|
|
@ -174,8 +174,8 @@ export default function usePresets() {
|
|||
const currentEndpointType = getEndpointField(endpointsConfig, endpoint, 'type');
|
||||
const endpointType = getEndpointField(endpointsConfig, newPreset.endpoint, 'type');
|
||||
const isAssistantSwitch =
|
||||
newPreset.endpoint === EModelEndpoint.assistants &&
|
||||
conversation?.endpoint === EModelEndpoint.assistants &&
|
||||
isAssistantsEndpoint(newPreset.endpoint) &&
|
||||
isAssistantsEndpoint(conversation?.endpoint) &&
|
||||
conversation?.endpoint === newPreset.endpoint;
|
||||
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import debounce from 'lodash/debounce';
|
||||
import { FileSources } from 'librechat-data-provider';
|
||||
import { FileSources, EToolResources } from 'librechat-data-provider';
|
||||
import { useCallback, useState, useEffect } from 'react';
|
||||
import type {
|
||||
BatchFile,
|
||||
|
|
@ -16,18 +16,20 @@ type FileMapSetter = GenericSetter<Map<string, ExtendedFile>>;
|
|||
const useFileDeletion = ({
|
||||
mutateAsync,
|
||||
assistant_id,
|
||||
tool_resource,
|
||||
}: {
|
||||
mutateAsync: UseMutateAsyncFunction<DeleteFilesResponse, unknown, DeleteFilesBody, unknown>;
|
||||
assistant_id?: string;
|
||||
tool_resource?: EToolResources;
|
||||
}) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [_batch, setFileDeleteBatch] = useState<BatchFile[]>([]);
|
||||
const setFilesToDelete = useSetFilesToDelete();
|
||||
|
||||
const executeBatchDelete = useCallback(
|
||||
(filesToDelete: BatchFile[], assistant_id?: string) => {
|
||||
console.log('Deleting files:', filesToDelete, assistant_id);
|
||||
mutateAsync({ files: filesToDelete, assistant_id });
|
||||
(filesToDelete: BatchFile[], assistant_id?: string, tool_resource?: EToolResources) => {
|
||||
console.log('Deleting files:', filesToDelete, assistant_id, tool_resource);
|
||||
mutateAsync({ files: filesToDelete, assistant_id, tool_resource });
|
||||
setFileDeleteBatch([]);
|
||||
},
|
||||
[mutateAsync],
|
||||
|
|
@ -81,11 +83,11 @@ const useFileDeletion = ({
|
|||
|
||||
setFileDeleteBatch((prevBatch) => {
|
||||
const newBatch = [...prevBatch, file];
|
||||
debouncedDelete(newBatch, assistant_id);
|
||||
debouncedDelete(newBatch, assistant_id, tool_resource);
|
||||
return newBatch;
|
||||
});
|
||||
},
|
||||
[debouncedDelete, setFilesToDelete, assistant_id],
|
||||
[debouncedDelete, setFilesToDelete, assistant_id, tool_resource],
|
||||
);
|
||||
|
||||
const deleteFiles = useCallback(
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
import { v4 } from 'uuid';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
megabyte,
|
||||
QueryKeys,
|
||||
EModelEndpoint,
|
||||
codeTypeMapping,
|
||||
mergeFileConfig,
|
||||
isAssistantsEndpoint,
|
||||
defaultAssistantsVersion,
|
||||
fileConfig as defaultFileConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import type { TEndpointsConfig } from 'librechat-data-provider';
|
||||
import type { ExtendedFile, FileSetter } from '~/common';
|
||||
import { useUploadFileMutation, useGetFileConfig } from '~/data-provider';
|
||||
import { useDelayedUploadToast } from './useDelayedUploadToast';
|
||||
|
|
@ -20,10 +25,12 @@ const { checkType } = defaultFileConfig;
|
|||
type UseFileHandling = {
|
||||
overrideEndpoint?: EModelEndpoint;
|
||||
fileSetter?: FileSetter;
|
||||
additionalMetadata?: Record<string, string>;
|
||||
fileFilter?: (file: File) => boolean;
|
||||
additionalMetadata?: Record<string, string | undefined>;
|
||||
};
|
||||
|
||||
const useFileHandling = (params?: UseFileHandling) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { showToast } = useToastContext();
|
||||
const [errors, setErrors] = useState<string[]>([]);
|
||||
const { startUploadTimer, clearUploadTimer } = useDelayedUploadToast();
|
||||
|
|
@ -141,15 +148,20 @@ const useFileHandling = (params?: UseFileHandling) => {
|
|||
|
||||
if (params?.additionalMetadata) {
|
||||
for (const [key, value] of Object.entries(params.additionalMetadata)) {
|
||||
formData.append(key, value);
|
||||
if (value) {
|
||||
formData.append(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
endpoint === EModelEndpoint.assistants &&
|
||||
isAssistantsEndpoint(endpoint) &&
|
||||
!formData.get('assistant_id') &&
|
||||
conversation?.assistant_id
|
||||
) {
|
||||
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
formData.append('version', version);
|
||||
formData.append('assistant_id', conversation.assistant_id);
|
||||
formData.append('model', conversation?.model ?? '');
|
||||
formData.append('message_file', 'true');
|
||||
|
|
|
|||
|
|
@ -5,15 +5,42 @@ import {
|
|||
useGetEndpointsQuery,
|
||||
} from 'librechat-data-provider/react-query';
|
||||
import { getConfigDefaults, EModelEndpoint, alternateName } from 'librechat-data-provider';
|
||||
import type { Assistant } from 'librechat-data-provider';
|
||||
import { useGetPresetsQuery, useListAssistantsQuery } from '~/data-provider';
|
||||
import type { AssistantsEndpoint, TAssistantsMap, TEndpointsConfig } from 'librechat-data-provider';
|
||||
import type { MentionOption } from '~/common';
|
||||
import useAssistantListMap from '~/hooks/Assistants/useAssistantListMap';
|
||||
import { mapEndpoints, getPresetTitle } from '~/utils';
|
||||
import { EndpointIcon } from '~/components/Endpoints';
|
||||
import { useGetPresetsQuery } from '~/data-provider';
|
||||
import useSelectMention from './useSelectMention';
|
||||
|
||||
const defaultInterface = getConfigDefaults().interface;
|
||||
|
||||
export default function useMentions({ assistantMap }: { assistantMap: Record<string, Assistant> }) {
|
||||
const assistantMapFn =
|
||||
({
|
||||
endpoint,
|
||||
assistantMap,
|
||||
endpointsConfig,
|
||||
}: {
|
||||
endpoint: AssistantsEndpoint;
|
||||
assistantMap: TAssistantsMap;
|
||||
endpointsConfig: TEndpointsConfig;
|
||||
}) =>
|
||||
({ id, name, description }) => ({
|
||||
type: endpoint,
|
||||
label: name ?? '',
|
||||
value: id,
|
||||
description: description ?? '',
|
||||
icon: EndpointIcon({
|
||||
conversation: { assistant_id: id, endpoint },
|
||||
containerClassName: 'shadow-stroke overflow-hidden rounded-full',
|
||||
endpointsConfig: endpointsConfig,
|
||||
context: 'menu-item',
|
||||
assistantMap,
|
||||
size: 20,
|
||||
}),
|
||||
});
|
||||
|
||||
export default function useMentions({ assistantMap }: { assistantMap: TAssistantsMap }) {
|
||||
const { data: presets } = useGetPresetsQuery();
|
||||
const { data: modelsConfig } = useGetModelsQuery();
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
|
|
@ -21,30 +48,43 @@ export default function useMentions({ assistantMap }: { assistantMap: Record<str
|
|||
const { data: endpoints = [] } = useGetEndpointsQuery({
|
||||
select: mapEndpoints,
|
||||
});
|
||||
const { data: assistants = [] } = useListAssistantsQuery(undefined, {
|
||||
select: (res) =>
|
||||
res.data
|
||||
.map(({ id, name, description }) => ({
|
||||
type: 'assistant',
|
||||
label: name ?? '',
|
||||
value: id,
|
||||
description: description ?? '',
|
||||
icon: EndpointIcon({
|
||||
conversation: { assistant_id: id, endpoint: EModelEndpoint.assistants },
|
||||
containerClassName: 'shadow-stroke overflow-hidden rounded-full',
|
||||
endpointsConfig: endpointsConfig,
|
||||
context: 'menu-item',
|
||||
const listMap = useAssistantListMap((res) =>
|
||||
res.data.map(({ id, name, description }) => ({
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
})),
|
||||
);
|
||||
const assistantListMap = useMemo(
|
||||
() => ({
|
||||
[EModelEndpoint.assistants]: listMap[EModelEndpoint.assistants]
|
||||
?.map(
|
||||
assistantMapFn({
|
||||
endpoint: EModelEndpoint.assistants,
|
||||
assistantMap,
|
||||
size: 20,
|
||||
endpointsConfig,
|
||||
}),
|
||||
}))
|
||||
.filter(Boolean),
|
||||
});
|
||||
)
|
||||
?.filter(Boolean),
|
||||
[EModelEndpoint.azureAssistants]: listMap[EModelEndpoint.azureAssistants]
|
||||
?.map(
|
||||
assistantMapFn({
|
||||
endpoint: EModelEndpoint.azureAssistants,
|
||||
assistantMap,
|
||||
endpointsConfig,
|
||||
}),
|
||||
)
|
||||
?.filter(Boolean),
|
||||
}),
|
||||
[listMap, assistantMap, endpointsConfig],
|
||||
);
|
||||
|
||||
const modelSpecs = useMemo(() => startupConfig?.modelSpecs?.list ?? [], [startupConfig]);
|
||||
const interfaceConfig = useMemo(
|
||||
() => startupConfig?.interface ?? defaultInterface,
|
||||
[startupConfig],
|
||||
);
|
||||
|
||||
const { onSelectMention } = useSelectMention({
|
||||
modelSpecs,
|
||||
endpointsConfig,
|
||||
|
|
@ -52,7 +92,7 @@ export default function useMentions({ assistantMap }: { assistantMap: Record<str
|
|||
assistantMap,
|
||||
});
|
||||
|
||||
const options = useMemo(() => {
|
||||
const options: MentionOption[] = useMemo(() => {
|
||||
const mentions = [
|
||||
...(modelSpecs?.length > 0 ? modelSpecs : []).map((modelSpec) => ({
|
||||
value: modelSpec.name,
|
||||
|
|
@ -67,12 +107,12 @@ export default function useMentions({ assistantMap }: { assistantMap: Record<str
|
|||
context: 'menu-item',
|
||||
size: 20,
|
||||
}),
|
||||
type: 'modelSpec',
|
||||
type: 'modelSpec' as const,
|
||||
})),
|
||||
...(interfaceConfig.endpointsMenu ? endpoints : []).map((endpoint) => ({
|
||||
value: endpoint,
|
||||
label: alternateName[endpoint] ?? endpoint ?? '',
|
||||
type: 'endpoint',
|
||||
type: 'endpoint' as const,
|
||||
icon: EndpointIcon({
|
||||
conversation: { endpoint },
|
||||
endpointsConfig,
|
||||
|
|
@ -80,7 +120,12 @@ export default function useMentions({ assistantMap }: { assistantMap: Record<str
|
|||
size: 20,
|
||||
}),
|
||||
})),
|
||||
...(endpointsConfig?.[EModelEndpoint.assistants] ? assistants : []),
|
||||
...(endpointsConfig?.[EModelEndpoint.assistants]
|
||||
? assistantListMap[EModelEndpoint.assistants]
|
||||
: []),
|
||||
...(endpointsConfig?.[EModelEndpoint.azureAssistants]
|
||||
? assistantListMap[EModelEndpoint.azureAssistants]
|
||||
: []),
|
||||
...((interfaceConfig.presets ? presets : [])?.map((preset, index) => ({
|
||||
value: preset.presetId ?? `preset-${index}`,
|
||||
label: preset.title ?? preset.modelLabel ?? preset.chatGptLabel ?? '',
|
||||
|
|
@ -93,7 +138,7 @@ export default function useMentions({ assistantMap }: { assistantMap: Record<str
|
|||
assistantMap,
|
||||
size: 20,
|
||||
}),
|
||||
type: 'preset',
|
||||
type: 'preset' as const,
|
||||
})) ?? []),
|
||||
];
|
||||
|
||||
|
|
@ -102,17 +147,17 @@ export default function useMentions({ assistantMap }: { assistantMap: Record<str
|
|||
presets,
|
||||
endpoints,
|
||||
modelSpecs,
|
||||
assistants,
|
||||
assistantMap,
|
||||
endpointsConfig,
|
||||
assistantListMap,
|
||||
interfaceConfig.presets,
|
||||
interfaceConfig.endpointsMenu,
|
||||
]);
|
||||
|
||||
return {
|
||||
options,
|
||||
assistants,
|
||||
modelsConfig,
|
||||
onSelectMention,
|
||||
assistantListMap,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type {
|
||||
TPreset,
|
||||
TModelSpec,
|
||||
TConversation,
|
||||
TAssistantsMap,
|
||||
TEndpointsConfig,
|
||||
TPreset,
|
||||
Assistant,
|
||||
} from 'librechat-data-provider';
|
||||
import type { MentionOption } from '~/common';
|
||||
import { getConvoSwitchLogic, getModelSpecIconURL, removeUnavailableTools } from '~/utils';
|
||||
|
|
@ -23,7 +23,7 @@ export default function useSelectMention({
|
|||
presets?: TPreset[];
|
||||
modelSpecs: TModelSpec[];
|
||||
endpointsConfig: TEndpointsConfig;
|
||||
assistantMap: Record<string, Assistant>;
|
||||
assistantMap: TAssistantsMap;
|
||||
}) {
|
||||
const { conversation } = useChatContext();
|
||||
const { newConversation } = useNewConvo();
|
||||
|
|
@ -194,10 +194,10 @@ export default function useSelectMention({
|
|||
onSelectEndpoint(key, { model: option.label });
|
||||
} else if (option.type === 'endpoint') {
|
||||
onSelectEndpoint(key);
|
||||
} else if (option.type === 'assistant') {
|
||||
onSelectEndpoint(EModelEndpoint.assistants, {
|
||||
} else if (isAssistantsEndpoint(option.type)) {
|
||||
onSelectEndpoint(option.type, {
|
||||
assistant_id: key,
|
||||
model: assistantMap?.[key]?.model ?? '',
|
||||
model: assistantMap?.[option.type]?.[key]?.model ?? '',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import debounce from 'lodash/debounce';
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import type { TEndpointOption } from 'librechat-data-provider';
|
||||
import type { KeyboardEvent } from 'react';
|
||||
|
|
@ -45,10 +45,11 @@ export default function useTextarea({
|
|||
const { conversationId, jailbreak, endpoint = '', assistant_id } = conversation || {};
|
||||
const isNotAppendable =
|
||||
((latestMessage?.unfinished && !isSubmitting) || latestMessage?.error) &&
|
||||
endpoint !== EModelEndpoint.assistants;
|
||||
!isAssistantsEndpoint(endpoint);
|
||||
// && (conversationId?.length ?? 0) > 6; // also ensures that we don't show the wrong placeholder
|
||||
|
||||
const assistant = endpoint === EModelEndpoint.assistants && assistantMap?.[assistant_id ?? ''];
|
||||
const assistant =
|
||||
isAssistantsEndpoint(endpoint) && assistantMap?.[endpoint ?? '']?.[assistant_id ?? ''];
|
||||
const assistantName = (assistant && assistant?.name) || '';
|
||||
|
||||
// auto focus to input, when enter a conversation.
|
||||
|
|
@ -86,9 +87,11 @@ export default function useTextarea({
|
|||
if (disabled) {
|
||||
return localize('com_endpoint_config_placeholder');
|
||||
}
|
||||
const currentEndpoint = conversation?.endpoint ?? '';
|
||||
const currentAssistantId = conversation?.assistant_id ?? '';
|
||||
if (
|
||||
conversation?.endpoint === EModelEndpoint.assistants &&
|
||||
(!conversation?.assistant_id || !assistantMap?.[conversation?.assistant_id ?? ''])
|
||||
isAssistantsEndpoint(currentEndpoint) &&
|
||||
(!currentAssistantId || !assistantMap?.[currentEndpoint]?.[currentAssistantId ?? ''])
|
||||
) {
|
||||
return localize('com_endpoint_assistant_placeholder');
|
||||
}
|
||||
|
|
@ -97,10 +100,9 @@ export default function useTextarea({
|
|||
return localize('com_endpoint_message_not_appendable');
|
||||
}
|
||||
|
||||
const sender =
|
||||
conversation?.endpoint === EModelEndpoint.assistants
|
||||
? getAssistantName({ name: assistantName, localize })
|
||||
: getSender(conversation as TEndpointOption);
|
||||
const sender = isAssistantsEndpoint(currentEndpoint)
|
||||
? getAssistantName({ name: assistantName, localize })
|
||||
: getSender(conversation as TEndpointOption);
|
||||
|
||||
return `${localize('com_endpoint_message')} ${sender ? sender : 'ChatGPT'}…`;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { TMessageProps } from '~/common';
|
||||
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
||||
import useCopyToClipboard from './useCopyToClipboard';
|
||||
|
|
@ -55,7 +55,8 @@ export default function useMessageHelpers(props: TMessageProps) {
|
|||
}, [isSubmitting, setAbortScroll]);
|
||||
|
||||
const assistant =
|
||||
conversation?.endpoint === EModelEndpoint.assistants && assistantMap?.[message?.model ?? ''];
|
||||
isAssistantsEndpoint(conversation?.endpoint) &&
|
||||
assistantMap?.[conversation?.endpoint ?? '']?.[message?.model ?? ''];
|
||||
|
||||
const regenerateMessage = () => {
|
||||
if ((isSubmitting && isCreatedByUser) || !message) {
|
||||
|
|
|
|||
|
|
@ -1,35 +1,44 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
|
||||
export default function useProgress(initialProgress = 0.01, increment = 0.007, fileSize?: number) {
|
||||
const calculateIncrement = (size?: number) => {
|
||||
const baseRate = 0.05;
|
||||
const minRate = 0.002;
|
||||
const sizeMB = size ? size / (1024 * 1024) : 0;
|
||||
const calculateIncrement = useCallback(
|
||||
(size?: number) => {
|
||||
const baseRate = 0.05;
|
||||
const minRate = 0.002;
|
||||
const sizeMB = size ? size / (1024 * 1024) : 0;
|
||||
|
||||
if (!size) {
|
||||
return increment;
|
||||
}
|
||||
if (!size) {
|
||||
return increment;
|
||||
}
|
||||
|
||||
if (sizeMB <= 1) {
|
||||
return baseRate * 2;
|
||||
} else {
|
||||
return Math.max(baseRate / Math.sqrt(sizeMB), minRate);
|
||||
}
|
||||
};
|
||||
if (sizeMB <= 1) {
|
||||
return baseRate * 2;
|
||||
} else {
|
||||
return Math.max(baseRate / Math.sqrt(sizeMB), minRate);
|
||||
}
|
||||
},
|
||||
[increment],
|
||||
);
|
||||
|
||||
const incrementValue = calculateIncrement(fileSize);
|
||||
const incrementValue = useMemo(
|
||||
() => calculateIncrement(fileSize),
|
||||
[fileSize, calculateIncrement],
|
||||
);
|
||||
const [progress, setProgress] = useState(initialProgress);
|
||||
|
||||
const getDynamicIncrement = (currentProgress: number) => {
|
||||
if (!fileSize) {
|
||||
return incrementValue;
|
||||
}
|
||||
if (currentProgress < 0.7) {
|
||||
return incrementValue;
|
||||
} else {
|
||||
return Math.max(0.0005, incrementValue * (1 - currentProgress));
|
||||
}
|
||||
};
|
||||
const getDynamicIncrement = useCallback(
|
||||
(currentProgress: number) => {
|
||||
if (!fileSize) {
|
||||
return incrementValue;
|
||||
}
|
||||
if (currentProgress < 0.7) {
|
||||
return incrementValue;
|
||||
} else {
|
||||
return Math.max(0.0005, incrementValue * (1 - currentProgress));
|
||||
}
|
||||
},
|
||||
[incrementValue, fileSize],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
|
@ -58,7 +67,7 @@ export default function useProgress(initialProgress = 0.01, increment = 0.007, f
|
|||
clearInterval(timer);
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [progress, initialProgress, incrementValue, fileSize]);
|
||||
}, [progress, initialProgress, incrementValue, fileSize, getDynamicIncrement]);
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
ArrowRightToLine,
|
||||
// Settings2,
|
||||
} from 'lucide-react';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { TConfig, TInterfaceConfig } from 'librechat-data-provider';
|
||||
import type { NavLink } from '~/common';
|
||||
import PanelSwitch from '~/components/SidePanel/Builder/PanelSwitch';
|
||||
|
|
@ -26,7 +26,7 @@ export default function useSideNavLinks({
|
|||
}) {
|
||||
const Links = useMemo(() => {
|
||||
const links: NavLink[] = [];
|
||||
// if (endpoint !== EModelEndpoint.assistants) {
|
||||
// if (!isAssistantsEndpoint(endpoint)) {
|
||||
// links.push({
|
||||
// title: 'com_sidepanel_parameters',
|
||||
// label: '',
|
||||
|
|
@ -36,7 +36,7 @@ export default function useSideNavLinks({
|
|||
// });
|
||||
// }
|
||||
if (
|
||||
endpoint === EModelEndpoint.assistants &&
|
||||
isAssistantsEndpoint(endpoint) &&
|
||||
assistants &&
|
||||
assistants.disableBuilder !== true &&
|
||||
keyProvided &&
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ import {
|
|||
createPayload,
|
||||
tPresetSchema,
|
||||
tMessageSchema,
|
||||
EModelEndpoint,
|
||||
LocalStorageKeys,
|
||||
tConvoUpdateSchema,
|
||||
removeNullishValues,
|
||||
isAssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import { useGetUserBalance, useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||
import type {
|
||||
|
|
@ -441,7 +441,7 @@ export default function useSSE(submission: TSubmission | null, index = 0) {
|
|||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
abortKey: _endpoint === EModelEndpoint.assistants ? runAbortKey : conversationId,
|
||||
abortKey: isAssistantsEndpoint(_endpoint) ? runAbortKey : conversationId,
|
||||
endpoint,
|
||||
}),
|
||||
});
|
||||
|
|
@ -513,7 +513,7 @@ export default function useSSE(submission: TSubmission | null, index = 0) {
|
|||
|
||||
const payloadData = createPayload(submission);
|
||||
let { payload } = payloadData;
|
||||
if (payload.endpoint === EModelEndpoint.assistants) {
|
||||
if (isAssistantsEndpoint(payload.endpoint)) {
|
||||
payload = removeNullishValues(payload);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,10 +18,8 @@ export { default as useNewConvo } from './useNewConvo';
|
|||
export { default as useLocalize } from './useLocalize';
|
||||
export { default as useMediaQuery } from './useMediaQuery';
|
||||
export { default as useChatHelpers } from './useChatHelpers';
|
||||
export { default as useGenerations } from './useGenerations';
|
||||
export { default as useScrollToRef } from './useScrollToRef';
|
||||
export { default as useLocalStorage } from './useLocalStorage';
|
||||
export { default as useDelayedRender } from './useDelayedRender';
|
||||
export { default as useOnClickOutside } from './useOnClickOutside';
|
||||
export { default as useOriginNavigate } from './useOriginNavigate';
|
||||
export { default as useGenerationsByLatest } from './useGenerationsByLatest';
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import { useCallback, useState } from 'react';
|
|||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
Constants,
|
||||
EModelEndpoint,
|
||||
QueryKeys,
|
||||
parseCompactConvo,
|
||||
ContentTypes,
|
||||
parseCompactConvo,
|
||||
isAssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import { useRecoilState, useResetRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { useGetMessagesByConvoId } from 'librechat-data-provider/react-query';
|
||||
|
|
@ -215,7 +215,7 @@ export default function useChatHelpers(index = 0, paramId: string | undefined) {
|
|||
error: false,
|
||||
};
|
||||
|
||||
if (endpoint === EModelEndpoint.assistants) {
|
||||
if (isAssistantsEndpoint(endpoint)) {
|
||||
initialResponse.model = conversation?.assistant_id ?? '';
|
||||
initialResponse.text = '';
|
||||
initialResponse.content = [
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
import type { TMessage } from 'librechat-data-provider';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import store from '~/store';
|
||||
|
||||
type TUseGenerations = {
|
||||
endpoint?: string;
|
||||
message: TMessage;
|
||||
isSubmitting: boolean;
|
||||
isEditing?: boolean;
|
||||
latestMessage?: TMessage | null;
|
||||
};
|
||||
|
||||
export default function useGenerations({
|
||||
endpoint,
|
||||
message,
|
||||
isSubmitting,
|
||||
isEditing = false,
|
||||
latestMessage: _latestMessage,
|
||||
}: TUseGenerations) {
|
||||
const latestMessage = useRecoilValue(store.latestMessage) ?? _latestMessage;
|
||||
|
||||
const { error, messageId, searchResult, finish_reason, isCreatedByUser } = message ?? {};
|
||||
const isEditableEndpoint = !![
|
||||
EModelEndpoint.openAI,
|
||||
EModelEndpoint.google,
|
||||
EModelEndpoint.assistants,
|
||||
EModelEndpoint.anthropic,
|
||||
EModelEndpoint.gptPlugins,
|
||||
EModelEndpoint.azureOpenAI,
|
||||
].find((e) => e === endpoint);
|
||||
|
||||
const continueSupported =
|
||||
latestMessage?.messageId === messageId &&
|
||||
finish_reason &&
|
||||
finish_reason !== 'stop' &&
|
||||
!isEditing &&
|
||||
!searchResult &&
|
||||
isEditableEndpoint;
|
||||
|
||||
const branchingSupported =
|
||||
// 5/21/23: Bing is allowing editing and Message regenerating
|
||||
!![
|
||||
EModelEndpoint.azureOpenAI,
|
||||
EModelEndpoint.openAI,
|
||||
EModelEndpoint.chatGPTBrowser,
|
||||
EModelEndpoint.google,
|
||||
EModelEndpoint.bingAI,
|
||||
EModelEndpoint.gptPlugins,
|
||||
EModelEndpoint.anthropic,
|
||||
].find((e) => e === endpoint);
|
||||
|
||||
const regenerateEnabled =
|
||||
!isCreatedByUser && !searchResult && !isEditing && !isSubmitting && branchingSupported;
|
||||
|
||||
const hideEditButton =
|
||||
isSubmitting ||
|
||||
error ||
|
||||
searchResult ||
|
||||
!branchingSupported ||
|
||||
(!isEditableEndpoint && !isCreatedByUser);
|
||||
|
||||
return {
|
||||
continueSupported,
|
||||
regenerateEnabled,
|
||||
hideEditButton,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { TMessage } from 'librechat-data-provider';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
|
||||
type TUseGenerations = {
|
||||
endpoint?: string;
|
||||
|
|
@ -21,7 +21,6 @@ export default function useGenerationsByLatest({
|
|||
EModelEndpoint.openAI,
|
||||
EModelEndpoint.custom,
|
||||
EModelEndpoint.google,
|
||||
EModelEndpoint.assistants,
|
||||
EModelEndpoint.anthropic,
|
||||
EModelEndpoint.gptPlugins,
|
||||
EModelEndpoint.azureOpenAI,
|
||||
|
|
@ -58,12 +57,13 @@ export default function useGenerationsByLatest({
|
|||
!branchingSupported ||
|
||||
(!isEditableEndpoint && !isCreatedByUser);
|
||||
|
||||
const forkingSupported = endpoint !== EModelEndpoint.assistants && !searchResult;
|
||||
const forkingSupported = !isAssistantsEndpoint(endpoint) && !searchResult;
|
||||
|
||||
return {
|
||||
forkingSupported,
|
||||
continueSupported,
|
||||
regenerateEnabled,
|
||||
isEditableEndpoint,
|
||||
hideEditButton,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,8 @@ import {
|
|||
useGetStartupConfig,
|
||||
useGetEndpointsQuery,
|
||||
} from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
FileSources,
|
||||
EModelEndpoint,
|
||||
LocalStorageKeys,
|
||||
defaultOrderQuery,
|
||||
} from 'librechat-data-provider';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { FileSources, LocalStorageKeys, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import {
|
||||
useRecoilState,
|
||||
useRecoilValue,
|
||||
|
|
@ -24,6 +20,7 @@ import type {
|
|||
TConversation,
|
||||
TEndpointsConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import type { AssistantListItem } from '~/common';
|
||||
import {
|
||||
getEndpointField,
|
||||
buildDefaultConvo,
|
||||
|
|
@ -32,13 +29,14 @@ import {
|
|||
getModelSpecIconURL,
|
||||
updateLastSelectedModel,
|
||||
} from '~/utils';
|
||||
import { useDeleteFilesMutation, useListAssistantsQuery } from '~/data-provider';
|
||||
import useOriginNavigate from './useOriginNavigate';
|
||||
import useAssistantListMap from './Assistants/useAssistantListMap';
|
||||
import { useDeleteFilesMutation } from '~/data-provider';
|
||||
|
||||
import { mainTextareaId } from '~/common';
|
||||
import store from '~/store';
|
||||
|
||||
const useNewConvo = (index = 0) => {
|
||||
const navigate = useOriginNavigate();
|
||||
const navigate = useNavigate();
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const defaultPreset = useRecoilValue(store.defaultPreset);
|
||||
const { setConversation } = store.useCreateConversationAtom(index);
|
||||
|
|
@ -48,11 +46,7 @@ const useNewConvo = (index = 0) => {
|
|||
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
|
||||
const modelsQuery = useGetModelsQuery();
|
||||
const timeoutIdRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
const { data: assistants = [] } = useListAssistantsQuery(defaultOrderQuery, {
|
||||
select: (res) =>
|
||||
res.data.map(({ id, name, metadata, model }) => ({ id, name, metadata, model })),
|
||||
});
|
||||
const assistantsListMap = useAssistantListMap();
|
||||
|
||||
const { mutateAsync } = useDeleteFilesMutation({
|
||||
onSuccess: () => {
|
||||
|
|
@ -100,12 +94,21 @@ const useNewConvo = (index = 0) => {
|
|||
conversation.endpointType = undefined;
|
||||
}
|
||||
|
||||
const isAssistantEndpoint = defaultEndpoint === EModelEndpoint.assistants;
|
||||
const isAssistantEndpoint = isAssistantsEndpoint(defaultEndpoint);
|
||||
const assistants: AssistantListItem[] = assistantsListMap[defaultEndpoint] ?? [];
|
||||
|
||||
if (
|
||||
conversation.assistant_id &&
|
||||
!assistantsListMap[defaultEndpoint]?.[conversation.assistant_id]
|
||||
) {
|
||||
conversation.assistant_id = undefined;
|
||||
}
|
||||
|
||||
if (!conversation.assistant_id && isAssistantEndpoint) {
|
||||
conversation.assistant_id =
|
||||
localStorage.getItem(`${LocalStorageKeys.ASST_ID_PREFIX}${index}`) ??
|
||||
assistants[0]?.id;
|
||||
localStorage.getItem(
|
||||
`${LocalStorageKeys.ASST_ID_PREFIX}${index}${defaultEndpoint}`,
|
||||
) ?? assistants[0]?.id;
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
@ -116,7 +119,7 @@ const useNewConvo = (index = 0) => {
|
|||
const assistant = assistants.find((asst) => asst.id === conversation.assistant_id);
|
||||
conversation.model = assistant?.model;
|
||||
updateLastSelectedModel({
|
||||
endpoint: EModelEndpoint.assistants,
|
||||
endpoint: defaultEndpoint,
|
||||
model: conversation.model,
|
||||
});
|
||||
}
|
||||
|
|
@ -145,7 +148,7 @@ const useNewConvo = (index = 0) => {
|
|||
if (appTitle) {
|
||||
document.title = appTitle;
|
||||
}
|
||||
navigate('new');
|
||||
navigate('/c/new');
|
||||
}
|
||||
|
||||
clearTimeout(timeoutIdRef.current);
|
||||
|
|
@ -156,7 +159,7 @@ const useNewConvo = (index = 0) => {
|
|||
}
|
||||
}, 150);
|
||||
},
|
||||
[endpointsConfig, defaultPreset, assistants, modelsQuery.data],
|
||||
[endpointsConfig, defaultPreset, assistantsListMap, modelsQuery.data],
|
||||
);
|
||||
|
||||
const newConversation = useCallback(
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
|
||||
const useOriginNavigate = () => {
|
||||
const _navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const navigate = (url?: string | null, opts = {}) => {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
const path = location.pathname.match(/^\/[^/]+\//);
|
||||
_navigate(`${path ? path[0] : '/c/'}${url}`, opts);
|
||||
};
|
||||
|
||||
return navigate;
|
||||
};
|
||||
|
||||
export default useOriginNavigate;
|
||||
|
|
@ -297,6 +297,15 @@ export default {
|
|||
com_nav_setting_general: 'عام',
|
||||
com_nav_setting_data: 'تحكم في البيانات',
|
||||
/* The following are AI translated */
|
||||
com_assistants_file_search: 'بحث الملفات',
|
||||
com_assistants_file_search_info:
|
||||
'لا يتم دعم إرفاق مخازن الكتل الرقمية لميزة البحث في الملفات بعد. يمكنك إرفاقها من ملعب المزود أو إرفاق ملفات إلى الرسائل للبحث في الملفات على أساس المحادثة.',
|
||||
com_assistants_non_retrieval_model:
|
||||
'البحث في الملفات غير مُمكّن على هذا النموذج. يرجى تحديد نموذج آخر.',
|
||||
com_ui_attach_error_openai: 'لا يمكن إرفاق ملفات المساعد إلى نقاط نهائية أخرى',
|
||||
com_ui_attach_warn_endpoint: 'قد يتم تجاهل الملفات غير المساعدة دون وجود أداة متوافقة',
|
||||
com_ui_assistant_deleted: 'تم حذف المساعد بنجاح',
|
||||
com_ui_assistant_delete_error: 'حدث خطأ أثناء حذف المساعد',
|
||||
com_ui_copied: 'تم النسخ',
|
||||
com_ui_copy_code: 'نسخ الكود',
|
||||
com_ui_copy_link: 'نسخ الرابط',
|
||||
|
|
@ -1636,6 +1645,36 @@ export const comparisons = {
|
|||
english: 'Data controls',
|
||||
translated: 'تحكم في البيانات',
|
||||
},
|
||||
com_assistants_file_search: {
|
||||
english: 'File Search',
|
||||
translated: 'بحث الملفات',
|
||||
},
|
||||
com_assistants_file_search_info: {
|
||||
english:
|
||||
'Attaching vector stores for File Search is not yet supported. You can attach them from the Provider Playground or attach files to messages for file search on a thread basis.',
|
||||
translated:
|
||||
'لا يتم دعم إرفاق مخازن الكتل الرقمية لميزة البحث في الملفات بعد. يمكنك إرفاقها من ملعب المزود أو إرفاق ملفات إلى الرسائل للبحث في الملفات على أساس المحادثة.',
|
||||
},
|
||||
com_assistants_non_retrieval_model: {
|
||||
english: 'File search is not enabled on this model. Please select another model.',
|
||||
translated: 'البحث في الملفات غير مُمكّن على هذا النموذج. يرجى تحديد نموذج آخر.',
|
||||
},
|
||||
com_ui_attach_error_openai: {
|
||||
english: 'Cannot attach Assistant files to other endpoints',
|
||||
translated: 'لا يمكن إرفاق ملفات المساعد إلى نقاط نهائية أخرى',
|
||||
},
|
||||
com_ui_attach_warn_endpoint: {
|
||||
english: 'Non-Assistant files may be ignored without a compatible tool',
|
||||
translated: 'قد يتم تجاهل الملفات غير المساعدة دون وجود أداة متوافقة',
|
||||
},
|
||||
com_ui_assistant_deleted: {
|
||||
english: 'Successfully deleted assistant',
|
||||
translated: 'تم حذف المساعد بنجاح',
|
||||
},
|
||||
com_ui_assistant_delete_error: {
|
||||
english: 'There was an error deleting the assistant',
|
||||
translated: 'حدث خطأ أثناء حذف المساعد',
|
||||
},
|
||||
com_ui_copied: {
|
||||
english: 'Copied!',
|
||||
translated: 'تم النسخ',
|
||||
|
|
|
|||
|
|
@ -481,6 +481,16 @@ export default {
|
|||
com_nav_setting_account: 'Konto',
|
||||
com_nav_language: 'Sprache',
|
||||
/* The following are AI Translated */
|
||||
com_assistants_file_search: 'Dateisuche',
|
||||
com_assistants_file_search_info:
|
||||
'Das Anhängen von Vektorspeichern für die Dateisuche wird derzeit noch nicht unterstützt. Du kannst sie im Provider Playground anhängen oder Dateien für die Dateisuche pro Thread anhängen.',
|
||||
com_assistants_non_retrieval_model:
|
||||
'Die Dateisuche ist für dieses Modell nicht aktiviert. Bitte wähle ein anderes Modell aus.',
|
||||
com_ui_attach_error_openai: 'Assistent-Dateien können nicht an andere Endpunkte angehängt werden',
|
||||
com_ui_attach_warn_endpoint:
|
||||
'Nicht-Assistent-Dateien könnten ohne ein kompatibles Werkzeug ignoriert werden',
|
||||
com_ui_assistant_deleted: 'Assistent erfolgreich gelöscht',
|
||||
com_ui_assistant_delete_error: 'Beim Löschen des Assistenten ist ein Fehler aufgetreten.',
|
||||
com_ui_copied: 'Kopiert',
|
||||
com_ui_copy_code: 'Code kopieren',
|
||||
com_ui_copy_link: 'Link kopieren',
|
||||
|
|
@ -2305,6 +2315,37 @@ export const comparisons = {
|
|||
english: 'Language',
|
||||
translated: 'Sprache',
|
||||
},
|
||||
com_assistants_file_search: {
|
||||
english: 'File Search',
|
||||
translated: 'Dateisuche',
|
||||
},
|
||||
com_assistants_file_search_info: {
|
||||
english:
|
||||
'Attaching vector stores for File Search is not yet supported. You can attach them from the Provider Playground or attach files to messages for file search on a thread basis.',
|
||||
translated:
|
||||
'Das Anhängen von Vektorspeichern für die Dateisuche wird derzeit noch nicht unterstützt. Du kannst sie im Provider Playground anhängen oder Dateien für die Dateisuche pro Thread anhängen.',
|
||||
},
|
||||
com_assistants_non_retrieval_model: {
|
||||
english: 'File search is not enabled on this model. Please select another model.',
|
||||
translated:
|
||||
'Die Dateisuche ist für dieses Modell nicht aktiviert. Bitte wähle ein anderes Modell aus.',
|
||||
},
|
||||
com_ui_attach_error_openai: {
|
||||
english: 'Cannot attach Assistant files to other endpoints',
|
||||
translated: 'Assistent-Dateien können nicht an andere Endpunkte angehängt werden',
|
||||
},
|
||||
com_ui_attach_warn_endpoint: {
|
||||
english: 'Non-Assistant files may be ignored without a compatible tool',
|
||||
translated: 'Nicht-Assistent-Dateien könnten ohne ein kompatibles Werkzeug ignoriert werden',
|
||||
},
|
||||
com_ui_assistant_deleted: {
|
||||
english: 'Successfully deleted assistant',
|
||||
translated: 'Assistent erfolgreich gelöscht',
|
||||
},
|
||||
com_ui_assistant_delete_error: {
|
||||
english: 'There was an error deleting the assistant',
|
||||
translated: 'Beim Löschen des Assistenten ist ein Fehler aufgetreten.',
|
||||
},
|
||||
com_ui_copied: {
|
||||
english: 'Copied!',
|
||||
translated: 'Kopiert',
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ export default {
|
|||
com_sidepanel_attach_files: 'Attach Files',
|
||||
com_sidepanel_manage_files: 'Manage Files',
|
||||
com_assistants_capabilities: 'Capabilities',
|
||||
com_assistants_file_search: 'File Search',
|
||||
com_assistants_file_search_info:
|
||||
'Attaching vector stores for File Search is not yet supported. You can attach them from the Provider Playground or attach files to messages for file search on a thread basis.',
|
||||
com_assistants_knowledge: 'Knowledge',
|
||||
com_assistants_knowledge_info:
|
||||
'If you upload files under Knowledge, conversations with your Assistant may include file contents.',
|
||||
|
|
@ -35,6 +38,8 @@ export default {
|
|||
com_assistants_actions: 'Actions',
|
||||
com_assistants_add_tools: 'Add Tools',
|
||||
com_assistants_add_actions: 'Add Actions',
|
||||
com_assistants_non_retrieval_model:
|
||||
'File search is not enabled on this model. Please select another model.',
|
||||
com_assistants_available_actions: 'Available Actions',
|
||||
com_assistants_running_action: 'Running action',
|
||||
com_assistants_completed_action: 'Talked to {0}',
|
||||
|
|
@ -73,6 +78,8 @@ export default {
|
|||
com_ui_field_required: 'This field is required',
|
||||
com_ui_download_error: 'Error downloading file. The file may have been deleted.',
|
||||
com_ui_attach_error_type: 'Unsupported file type for endpoint:',
|
||||
com_ui_attach_error_openai: 'Cannot attach Assistant files to other endpoints',
|
||||
com_ui_attach_warn_endpoint: 'Non-Assistant files may be ignored without a compatible tool',
|
||||
com_ui_attach_error_size: 'File size limit exceeded for endpoint:',
|
||||
com_ui_attach_error:
|
||||
'Cannot attach file. Create or select a conversation, or try refreshing the page.',
|
||||
|
|
@ -196,6 +203,8 @@ export default {
|
|||
com_ui_result: 'Result',
|
||||
com_ui_image_gen: 'Image Gen',
|
||||
com_ui_assistant: 'Assistant',
|
||||
com_ui_assistant_deleted: 'Successfully deleted assistant',
|
||||
com_ui_assistant_delete_error: 'There was an error deleting the assistant',
|
||||
com_ui_assistants: 'Assistants',
|
||||
com_ui_attachment: 'Attachment',
|
||||
com_ui_assistants_output: 'Assistants Output',
|
||||
|
|
|
|||
|
|
@ -475,6 +475,17 @@ export default {
|
|||
com_nav_lang_auto: 'Detección automática',
|
||||
com_nav_lang_spanish: 'Español',
|
||||
/* The following are AI Translated */
|
||||
com_assistants_file_search: 'Búsqueda de Archivos',
|
||||
com_assistants_file_search_info:
|
||||
'Adjuntar almacenes vectoriales para la Búsqueda de Archivos aún no está soportado. Puede adjuntarlos desde el Área de Pruebas del Proveedor o adjuntar archivos a los mensajes para la búsqueda de archivos en una conversación específica.',
|
||||
com_assistants_non_retrieval_model:
|
||||
'La búsqueda de archivos no está habilitada en este modelo. Por favor, seleccione otro modelo.',
|
||||
com_ui_attach_error_openai:
|
||||
'No se pueden adjuntar archivos del Asistente a otros puntos de conexión',
|
||||
com_ui_attach_warn_endpoint:
|
||||
'Es posible que los archivos no compatibles con la herramienta sean ignorados',
|
||||
com_ui_assistant_deleted: 'Asistente eliminado con éxito',
|
||||
com_ui_assistant_delete_error: 'Hubo un error al eliminar el asistente',
|
||||
com_ui_copied: '¡Copiado!',
|
||||
com_ui_copy_code: 'Copiar código',
|
||||
com_ui_copy_link: 'Copiar enlace',
|
||||
|
|
@ -2286,6 +2297,37 @@ export const comparisons = {
|
|||
english: 'Español',
|
||||
translated: 'Español',
|
||||
},
|
||||
com_assistants_file_search: {
|
||||
english: 'File Search',
|
||||
translated: 'Búsqueda de Archivos',
|
||||
},
|
||||
com_assistants_file_search_info: {
|
||||
english:
|
||||
'Attaching vector stores for File Search is not yet supported. You can attach them from the Provider Playground or attach files to messages for file search on a thread basis.',
|
||||
translated:
|
||||
'Adjuntar almacenes vectoriales para la Búsqueda de Archivos aún no está soportado. Puede adjuntarlos desde el Área de Pruebas del Proveedor o adjuntar archivos a los mensajes para la búsqueda de archivos en una conversación específica.',
|
||||
},
|
||||
com_assistants_non_retrieval_model: {
|
||||
english: 'File search is not enabled on this model. Please select another model.',
|
||||
translated:
|
||||
'La búsqueda de archivos no está habilitada en este modelo. Por favor, seleccione otro modelo.',
|
||||
},
|
||||
com_ui_attach_error_openai: {
|
||||
english: 'Cannot attach Assistant files to other endpoints',
|
||||
translated: 'No se pueden adjuntar archivos del Asistente a otros puntos de conexión',
|
||||
},
|
||||
com_ui_attach_warn_endpoint: {
|
||||
english: 'Non-Assistant files may be ignored without a compatible tool',
|
||||
translated: 'Es posible que los archivos no compatibles con la herramienta sean ignorados',
|
||||
},
|
||||
com_ui_assistant_deleted: {
|
||||
english: 'Successfully deleted assistant',
|
||||
translated: 'Asistente eliminado con éxito',
|
||||
},
|
||||
com_ui_assistant_delete_error: {
|
||||
english: 'There was an error deleting the assistant',
|
||||
translated: 'Hubo un error al eliminar el asistente',
|
||||
},
|
||||
com_ui_copied: {
|
||||
english: 'Copied!',
|
||||
translated: '¡Copiado!',
|
||||
|
|
|
|||
|
|
@ -364,6 +364,16 @@ export default {
|
|||
com_nav_setting_data: 'Contrôles des données',
|
||||
com_nav_setting_account: 'Compte',
|
||||
/* The following are AI Translated */
|
||||
com_assistants_file_search: 'Recherche de fichiers',
|
||||
com_assistants_file_search_info:
|
||||
'L\'ajout de vecteurs de stockage pour la recherche de fichiers n\'est pas encore pris en charge. Vous pouvez les ajouter depuis le terrain de jeu du fournisseur ou joindre des fichiers aux messages pour une recherche de fichiers au niveau du fil de discussion.',
|
||||
com_assistants_non_retrieval_model:
|
||||
'La recherche de fichiers n\'est pas activée pour ce modèle. Veuillez sélectionner un autre modèle.',
|
||||
com_ui_attach_error_openai:
|
||||
'Impossible de joindre les fichiers de l\'Assistant à d\'autres points d\'accès',
|
||||
com_ui_attach_warn_endpoint: 'Les fichiers non compatibles avec l\'outil peuvent être ignorés',
|
||||
com_ui_assistant_deleted: 'Assistant supprimé avec succès',
|
||||
com_ui_assistant_delete_error: 'Une erreur s\'est produite lors de la suppression de l\'assistant.',
|
||||
com_ui_copied: 'Copié !',
|
||||
com_ui_copy_code: 'Copier le code',
|
||||
com_ui_copy_link: 'Copier le lien',
|
||||
|
|
@ -1863,6 +1873,37 @@ export const comparisons = {
|
|||
english: 'Account',
|
||||
translated: 'Compte',
|
||||
},
|
||||
com_assistants_file_search: {
|
||||
english: 'File Search',
|
||||
translated: 'Recherche de fichiers',
|
||||
},
|
||||
com_assistants_file_search_info: {
|
||||
english:
|
||||
'Attaching vector stores for File Search is not yet supported. You can attach them from the Provider Playground or attach files to messages for file search on a thread basis.',
|
||||
translated:
|
||||
'L\'ajout de vecteurs de stockage pour la recherche de fichiers n\'est pas encore pris en charge. Vous pouvez les ajouter depuis le terrain de jeu du fournisseur ou joindre des fichiers aux messages pour une recherche de fichiers au niveau du fil de discussion.',
|
||||
},
|
||||
com_assistants_non_retrieval_model: {
|
||||
english: 'File search is not enabled on this model. Please select another model.',
|
||||
translated:
|
||||
'La recherche de fichiers n\'est pas activée pour ce modèle. Veuillez sélectionner un autre modèle.',
|
||||
},
|
||||
com_ui_attach_error_openai: {
|
||||
english: 'Cannot attach Assistant files to other endpoints',
|
||||
translated: 'Impossible de joindre les fichiers de l\'Assistant à d\'autres points d\'accès',
|
||||
},
|
||||
com_ui_attach_warn_endpoint: {
|
||||
english: 'Non-Assistant files may be ignored without a compatible tool',
|
||||
translated: 'Les fichiers non compatibles avec l\'outil peuvent être ignorés',
|
||||
},
|
||||
com_ui_assistant_deleted: {
|
||||
english: 'Successfully deleted assistant',
|
||||
translated: 'Assistant supprimé avec succès',
|
||||
},
|
||||
com_ui_assistant_delete_error: {
|
||||
english: 'There was an error deleting the assistant',
|
||||
translated: 'Une erreur s\'est produite lors de la suppression de l\'assistant.',
|
||||
},
|
||||
com_ui_copied: {
|
||||
english: 'Copied!',
|
||||
translated: 'Copié !',
|
||||
|
|
|
|||
|
|
@ -525,6 +525,16 @@ export default {
|
|||
com_nav_setting_data: 'Controlli dati',
|
||||
com_nav_setting_account: 'Account',
|
||||
/* The following are AI Translated */
|
||||
com_assistants_file_search: 'Ricerca File',
|
||||
com_assistants_file_search_info:
|
||||
'L\'aggiunta di archivi vettoriali per la Ricerca File non è ancora supportata. Puoi aggiungerli dal Provider Playground o allegare file ai messaggi per la ricerca file su base di thread.',
|
||||
com_assistants_non_retrieval_model:
|
||||
'La ricerca di file non è abilitata su questo modello. Seleziona un altro modello.',
|
||||
com_ui_attach_error_openai: 'Non è possibile allegare file dell\'Assistente ad altri endpoint',
|
||||
com_ui_attach_warn_endpoint:
|
||||
'Attenzione: i file non compatibili con lo strumento potrebbero essere ignorati',
|
||||
com_ui_assistant_deleted: 'Assistente eliminato con successo',
|
||||
com_ui_assistant_delete_error: 'Si è verificato un errore durante l\'eliminazione dell\'assistente',
|
||||
com_ui_copied: 'Copiato!',
|
||||
com_ui_copy_code: 'Copia codice',
|
||||
com_ui_copy_link: 'Copia link',
|
||||
|
|
@ -2443,6 +2453,36 @@ export const comparisons = {
|
|||
english: 'Account',
|
||||
translated: 'Account',
|
||||
},
|
||||
com_assistants_file_search: {
|
||||
english: 'File Search',
|
||||
translated: 'Ricerca File',
|
||||
},
|
||||
com_assistants_file_search_info: {
|
||||
english:
|
||||
'Attaching vector stores for File Search is not yet supported. You can attach them from the Provider Playground or attach files to messages for file search on a thread basis.',
|
||||
translated:
|
||||
'L\'aggiunta di archivi vettoriali per la Ricerca File non è ancora supportata. Puoi aggiungerli dal Provider Playground o allegare file ai messaggi per la ricerca file su base di thread.',
|
||||
},
|
||||
com_assistants_non_retrieval_model: {
|
||||
english: 'File search is not enabled on this model. Please select another model.',
|
||||
translated: 'La ricerca di file non è abilitata su questo modello. Seleziona un altro modello.',
|
||||
},
|
||||
com_ui_attach_error_openai: {
|
||||
english: 'Cannot attach Assistant files to other endpoints',
|
||||
translated: 'Non è possibile allegare file dell\'Assistente ad altri endpoint',
|
||||
},
|
||||
com_ui_attach_warn_endpoint: {
|
||||
english: 'Non-Assistant files may be ignored without a compatible tool',
|
||||
translated: 'Attenzione: i file non compatibili con lo strumento potrebbero essere ignorati',
|
||||
},
|
||||
com_ui_assistant_deleted: {
|
||||
english: 'Successfully deleted assistant',
|
||||
translated: 'Assistente eliminato con successo',
|
||||
},
|
||||
com_ui_assistant_delete_error: {
|
||||
english: 'There was an error deleting the assistant',
|
||||
translated: 'Si è verificato un errore durante l\'eliminazione dell\'assistente',
|
||||
},
|
||||
com_ui_copied: {
|
||||
english: 'Copied!',
|
||||
translated: 'Copiato!',
|
||||
|
|
|
|||
|
|
@ -473,6 +473,16 @@ export default {
|
|||
com_nav_setting_data: 'データ管理',
|
||||
com_nav_setting_account: 'アカウント',
|
||||
/* The following are AI translated */
|
||||
com_assistants_file_search: 'ファイル検索',
|
||||
com_assistants_file_search_info:
|
||||
'ファイル検索用のベクトル ストアを添付することはまだサポートされていません。Provider Playgroundからそれらを添付するか、スレッド単位でメッセージにファイルを添付してファイル検索を行うことができます。',
|
||||
com_assistants_non_retrieval_model:
|
||||
'このモデルではファイル検索機能は有効になっていません。別のモデルを選択してください。',
|
||||
com_ui_attach_error_openai: '他のエンドポイントにAssistantファイルを添付することはできません',
|
||||
com_ui_attach_warn_endpoint:
|
||||
'互換性のあるツールがない場合、非アシスタントのファイルは無視される可能性があります',
|
||||
com_ui_assistant_deleted: 'アシスタントが正常に削除されました',
|
||||
com_ui_assistant_delete_error: 'アシスタントの削除中にエラーが発生しました。',
|
||||
com_ui_copied: 'コピーしました',
|
||||
com_ui_copy_code: 'コードをコピーする',
|
||||
com_ui_copy_link: 'リンクをコピー',
|
||||
|
|
@ -2296,6 +2306,38 @@ export const comparisons = {
|
|||
english: 'Account',
|
||||
translated: 'アカウント',
|
||||
},
|
||||
com_assistants_file_search: {
|
||||
english: 'File Search',
|
||||
translated: 'ファイル検索',
|
||||
},
|
||||
com_assistants_file_search_info: {
|
||||
english:
|
||||
'Attaching vector stores for File Search is not yet supported. You can attach them from the Provider Playground or attach files to messages for file search on a thread basis.',
|
||||
translated:
|
||||
'ファイル検索用のベクトル ストアを添付することはまだサポートされていません。Provider Playgroundからそれらを添付するか、スレッド単位でメッセージにファイルを添付してファイル検索を行うことができます。',
|
||||
},
|
||||
com_assistants_non_retrieval_model: {
|
||||
english: 'File search is not enabled on this model. Please select another model.',
|
||||
translated:
|
||||
'このモデルではファイル検索機能は有効になっていません。別のモデルを選択してください。',
|
||||
},
|
||||
com_ui_attach_error_openai: {
|
||||
english: 'Cannot attach Assistant files to other endpoints',
|
||||
translated: '他のエンドポイントにAssistantファイルを添付することはできません',
|
||||
},
|
||||
com_ui_attach_warn_endpoint: {
|
||||
english: 'Non-Assistant files may be ignored without a compatible tool',
|
||||
translated:
|
||||
'互換性のあるツールがない場合、非アシスタントのファイルは無視される可能性があります',
|
||||
},
|
||||
com_ui_assistant_deleted: {
|
||||
english: 'Successfully deleted assistant',
|
||||
translated: 'アシスタントが正常に削除されました',
|
||||
},
|
||||
com_ui_assistant_delete_error: {
|
||||
english: 'There was an error deleting the assistant',
|
||||
translated: 'アシスタントの削除中にエラーが発生しました。',
|
||||
},
|
||||
com_ui_copied: {
|
||||
english: 'Copied!',
|
||||
translated: 'コピーしました',
|
||||
|
|
|
|||
|
|
@ -278,6 +278,15 @@ export default {
|
|||
com_nav_setting_general: '일반',
|
||||
com_nav_setting_data: '데이터 제어',
|
||||
/* The following are AI Translated */
|
||||
com_assistants_file_search: '파일 검색',
|
||||
com_assistants_file_search_info:
|
||||
'파일 검색을 위한 벡터 저장소 연결은 아직 지원되지 않습니다. Provider Playground에서 연결하거나 스레드 기반으로 메시지에 파일을 첨부하여 파일 검색을 할 수 있습니다.',
|
||||
com_assistants_non_retrieval_model:
|
||||
'이 모델에서는 파일 검색 기능을 사용할 수 없습니다. 다른 모델을 선택하세요.',
|
||||
com_ui_attach_error_openai: '어시스턴트 파일을 다른 엔드포인트에 첨부할 수 없습니다.',
|
||||
com_ui_attach_warn_endpoint: '호환되는 도구가 없으면 비어시스턴트 파일이 무시될 수 있습니다.',
|
||||
com_ui_assistant_deleted: '어시스턴트가 성공적으로 삭제되었습니다',
|
||||
com_ui_assistant_delete_error: '어시스턴트 삭제 중 오류가 발생했습니다.',
|
||||
com_ui_copied: '복사됨',
|
||||
com_ui_copy_code: '코드 복사',
|
||||
com_ui_copy_link: '링크 복사',
|
||||
|
|
@ -1581,6 +1590,36 @@ export const comparisons = {
|
|||
english: 'Data controls',
|
||||
translated: '데이터 제어',
|
||||
},
|
||||
com_assistants_file_search: {
|
||||
english: 'File Search',
|
||||
translated: '파일 검색',
|
||||
},
|
||||
com_assistants_file_search_info: {
|
||||
english:
|
||||
'Attaching vector stores for File Search is not yet supported. You can attach them from the Provider Playground or attach files to messages for file search on a thread basis.',
|
||||
translated:
|
||||
'파일 검색을 위한 벡터 저장소 연결은 아직 지원되지 않습니다. Provider Playground에서 연결하거나 스레드 기반으로 메시지에 파일을 첨부하여 파일 검색을 할 수 있습니다.',
|
||||
},
|
||||
com_assistants_non_retrieval_model: {
|
||||
english: 'File search is not enabled on this model. Please select another model.',
|
||||
translated: '이 모델에서는 파일 검색 기능을 사용할 수 없습니다. 다른 모델을 선택하세요.',
|
||||
},
|
||||
com_ui_attach_error_openai: {
|
||||
english: 'Cannot attach Assistant files to other endpoints',
|
||||
translated: '어시스턴트 파일을 다른 엔드포인트에 첨부할 수 없습니다.',
|
||||
},
|
||||
com_ui_attach_warn_endpoint: {
|
||||
english: 'Non-Assistant files may be ignored without a compatible tool',
|
||||
translated: '호환되는 도구가 없으면 비어시스턴트 파일이 무시될 수 있습니다.',
|
||||
},
|
||||
com_ui_assistant_deleted: {
|
||||
english: 'Successfully deleted assistant',
|
||||
translated: '어시스턴트가 성공적으로 삭제되었습니다',
|
||||
},
|
||||
com_ui_assistant_delete_error: {
|
||||
english: 'There was an error deleting the assistant',
|
||||
translated: '어시스턴트 삭제 중 오류가 발생했습니다.',
|
||||
},
|
||||
com_ui_copied: {
|
||||
english: 'Copied!',
|
||||
translated: '복사됨',
|
||||
|
|
|
|||
|
|
@ -381,6 +381,16 @@ export default {
|
|||
com_ui_upload_error: 'Произошла ошибка при загрузке вашего файла',
|
||||
com_user_message: 'Вы',
|
||||
/* The following are AI Translated */
|
||||
com_assistants_file_search: 'Поиск файлов',
|
||||
com_assistants_file_search_info:
|
||||
'Прикрепление векторных хранилищ для Поиска по файлам пока не поддерживается. Вы можете прикрепить их из Песочницы провайдера или прикрепить файлы к сообщениям для поиска по файлам в отдельных диалогах.',
|
||||
com_assistants_non_retrieval_model:
|
||||
'Поиск по файлам недоступен для этой модели. Пожалуйста, выберите другую модель.',
|
||||
com_ui_attach_error_openai: 'Невозможно прикрепить файлы ассистента к другим режимам',
|
||||
com_ui_attach_warn_endpoint:
|
||||
'Файлы сторонних приложений могут быть проигнорированы без совместимого плагина',
|
||||
com_ui_assistant_deleted: 'Ассистент успешно удален',
|
||||
com_ui_assistant_delete_error: 'Произошла ошибка при удалении ассистента',
|
||||
com_ui_copied: 'Скопировано',
|
||||
com_ui_copy_code: 'Копировать код',
|
||||
com_ui_copy_link: 'Копировать ссылку',
|
||||
|
|
@ -1948,6 +1958,36 @@ export const comparisons = {
|
|||
english: 'You',
|
||||
translated: 'Вы',
|
||||
},
|
||||
com_assistants_file_search: {
|
||||
english: 'File Search',
|
||||
translated: 'Поиск файлов',
|
||||
},
|
||||
com_assistants_file_search_info: {
|
||||
english:
|
||||
'Attaching vector stores for File Search is not yet supported. You can attach them from the Provider Playground or attach files to messages for file search on a thread basis.',
|
||||
translated:
|
||||
'Прикрепление векторных хранилищ для Поиска по файлам пока не поддерживается. Вы можете прикрепить их из Песочницы провайдера или прикрепить файлы к сообщениям для поиска по файлам в отдельных диалогах.',
|
||||
},
|
||||
com_assistants_non_retrieval_model: {
|
||||
english: 'File search is not enabled on this model. Please select another model.',
|
||||
translated: 'Поиск по файлам недоступен для этой модели. Пожалуйста, выберите другую модель.',
|
||||
},
|
||||
com_ui_attach_error_openai: {
|
||||
english: 'Cannot attach Assistant files to other endpoints',
|
||||
translated: 'Невозможно прикрепить файлы ассистента к другим режимам',
|
||||
},
|
||||
com_ui_attach_warn_endpoint: {
|
||||
english: 'Non-Assistant files may be ignored without a compatible tool',
|
||||
translated: 'Файлы сторонних приложений могут быть проигнорированы без совместимого плагина',
|
||||
},
|
||||
com_ui_assistant_deleted: {
|
||||
english: 'Successfully deleted assistant',
|
||||
translated: 'Ассистент успешно удален',
|
||||
},
|
||||
com_ui_assistant_delete_error: {
|
||||
english: 'There was an error deleting the assistant',
|
||||
translated: 'Произошла ошибка при удалении ассистента',
|
||||
},
|
||||
com_ui_copied: {
|
||||
english: 'Copied!',
|
||||
translated: 'Скопировано',
|
||||
|
|
|
|||
|
|
@ -434,6 +434,14 @@ export default {
|
|||
com_nav_setting_data: '数据管理',
|
||||
com_nav_setting_account: '账户',
|
||||
/* The following are AI Translated */
|
||||
com_assistants_file_search: '文件搜索',
|
||||
com_assistants_file_search_info:
|
||||
'暂不支持为文件搜索附加向量存储。您可以从提供程序游乐场附加它们,或者在线程基础上为文件搜索附加文件。',
|
||||
com_assistants_non_retrieval_model: '此模型未启用文件搜索功能。请选择其他模型。',
|
||||
com_ui_attach_error_openai: '无法将助手文件附加到其他渠道',
|
||||
com_ui_attach_warn_endpoint: '不兼容的工具可能会忽略非助手文件',
|
||||
com_ui_assistant_deleted: '助手已成功删除',
|
||||
com_ui_assistant_delete_error: '删除助手时出错。',
|
||||
com_ui_date_october: '十月',
|
||||
com_ui_date_november: '十一月',
|
||||
com_ui_date_december: '十二月',
|
||||
|
|
@ -2198,6 +2206,36 @@ export const comparisons = {
|
|||
english: 'Account',
|
||||
translated: '账户',
|
||||
},
|
||||
com_assistants_file_search: {
|
||||
english: 'File Search',
|
||||
translated: '文件搜索',
|
||||
},
|
||||
com_assistants_file_search_info: {
|
||||
english:
|
||||
'Attaching vector stores for File Search is not yet supported. You can attach them from the Provider Playground or attach files to messages for file search on a thread basis.',
|
||||
translated:
|
||||
'暂不支持为文件搜索附加向量存储。您可以从提供程序游乐场附加它们,或者在线程基础上为文件搜索附加文件。',
|
||||
},
|
||||
com_assistants_non_retrieval_model: {
|
||||
english: 'File search is not enabled on this model. Please select another model.',
|
||||
translated: '此模型未启用文件搜索功能。请选择其他模型。',
|
||||
},
|
||||
com_ui_attach_error_openai: {
|
||||
english: 'Cannot attach Assistant files to other endpoints',
|
||||
translated: '无法将助手文件附加到其他渠道',
|
||||
},
|
||||
com_ui_attach_warn_endpoint: {
|
||||
english: 'Non-Assistant files may be ignored without a compatible tool',
|
||||
translated: '不兼容的工具可能会忽略非助手文件',
|
||||
},
|
||||
com_ui_assistant_deleted: {
|
||||
english: 'Successfully deleted assistant',
|
||||
translated: '助手已成功删除',
|
||||
},
|
||||
com_ui_assistant_delete_error: {
|
||||
english: 'There was an error deleting the assistant',
|
||||
translated: '删除助手时出错。',
|
||||
},
|
||||
com_ui_date_october: {
|
||||
english: 'October',
|
||||
translated: '十月',
|
||||
|
|
|
|||
|
|
@ -283,6 +283,14 @@ export default {
|
|||
com_nav_setting_general: '一般',
|
||||
com_nav_setting_data: '資料控制',
|
||||
/* The following are AI translated */
|
||||
com_assistants_file_search: '檔案搜尋',
|
||||
com_assistants_file_search_info:
|
||||
'目前尚不支援為檔案搜尋附加向量存儲。您可以從提供者遊樂場附加它們,或在每個主題的基礎上為檔案搜尋附加檔案。',
|
||||
com_assistants_non_retrieval_model: '此模型未啟用檔案搜尋功能。請選擇其他模型。',
|
||||
com_ui_attach_error_openai: '無法將助理檔案附加至其他端點',
|
||||
com_ui_attach_warn_endpoint: '非相容工具的非助理檔案可能會被忽略',
|
||||
com_ui_assistant_deleted: '已成功刪除助理',
|
||||
com_ui_assistant_delete_error: '刪除助理時發生錯誤',
|
||||
com_ui_copied: '已複製!',
|
||||
com_ui_copy_code: '複製程式碼',
|
||||
com_ui_copy_link: '複製連結',
|
||||
|
|
@ -1611,6 +1619,36 @@ export const comparisons = {
|
|||
english: 'Data controls',
|
||||
translated: '資料控制',
|
||||
},
|
||||
com_assistants_file_search: {
|
||||
english: 'File Search',
|
||||
translated: '檔案搜尋',
|
||||
},
|
||||
com_assistants_file_search_info: {
|
||||
english:
|
||||
'Attaching vector stores for File Search is not yet supported. You can attach them from the Provider Playground or attach files to messages for file search on a thread basis.',
|
||||
translated:
|
||||
'目前尚不支援為檔案搜尋附加向量存儲。您可以從提供者遊樂場附加它們,或在每個主題的基礎上為檔案搜尋附加檔案。',
|
||||
},
|
||||
com_assistants_non_retrieval_model: {
|
||||
english: 'File search is not enabled on this model. Please select another model.',
|
||||
translated: '此模型未啟用檔案搜尋功能。請選擇其他模型。',
|
||||
},
|
||||
com_ui_attach_error_openai: {
|
||||
english: 'Cannot attach Assistant files to other endpoints',
|
||||
translated: '無法將助理檔案附加至其他端點',
|
||||
},
|
||||
com_ui_attach_warn_endpoint: {
|
||||
english: 'Non-Assistant files may be ignored without a compatible tool',
|
||||
translated: '非相容工具的非助理檔案可能會被忽略',
|
||||
},
|
||||
com_ui_assistant_deleted: {
|
||||
english: 'Successfully deleted assistant',
|
||||
translated: '已成功刪除助理',
|
||||
},
|
||||
com_ui_assistant_delete_error: {
|
||||
english: 'There was an error deleting the assistant',
|
||||
translated: '刪除助理時發生錯誤',
|
||||
},
|
||||
com_ui_copied: {
|
||||
english: 'Copied!',
|
||||
translated: '已複製!',
|
||||
|
|
|
|||
|
|
@ -270,4 +270,8 @@
|
|||
.radix-side-top\:animate-slideDownAndFade[data-side=top] {
|
||||
-webkit-animation:slideDownAndFade .4s cubic-bezier(.16,1,.3,1);
|
||||
animation:slideDownAndFade .4s cubic-bezier(.16,1,.3,1)
|
||||
}
|
||||
|
||||
.azure-bg-color {
|
||||
background: linear-gradient(0.375turn, #61bde2, #4389d0);
|
||||
}
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import { useEffect, useRef } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { defaultOrderQuery } from 'librechat-data-provider';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import {
|
||||
useGetModelsQuery,
|
||||
useGetStartupConfig,
|
||||
useGetEndpointsQuery,
|
||||
} from 'librechat-data-provider/react-query';
|
||||
import type { TPreset } from 'librechat-data-provider';
|
||||
import { useGetConvoIdQuery, useListAssistantsQuery } from '~/data-provider';
|
||||
import { useNewConvo, useAppStartup, useAssistantListMap } from '~/hooks';
|
||||
import { getDefaultModelSpec, getModelSpecIconURL } from '~/utils';
|
||||
import { useNewConvo, useAppStartup } from '~/hooks';
|
||||
import { useGetConvoIdQuery } from '~/data-provider';
|
||||
import ChatView from '~/components/Chat/ChatView';
|
||||
import useAuthRedirect from './useAuthRedirect';
|
||||
import { Spinner } from '~/components/svg';
|
||||
|
|
@ -35,10 +35,7 @@ export default function ChatRoute() {
|
|||
enabled: isAuthenticated && conversationId !== 'new',
|
||||
});
|
||||
const endpointsQuery = useGetEndpointsQuery({ enabled: isAuthenticated });
|
||||
const { data: assistants = null } = useListAssistantsQuery(defaultOrderQuery, {
|
||||
select: (res) =>
|
||||
res.data.map(({ id, name, metadata, model }) => ({ id, name, metadata, model })),
|
||||
});
|
||||
const assistantListMap = useAssistantListMap();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
|
|
@ -87,7 +84,8 @@ export default function ChatRoute() {
|
|||
!hasSetConversation.current &&
|
||||
!modelsQuery.data?.initial &&
|
||||
conversationId === 'new' &&
|
||||
assistants
|
||||
assistantListMap[EModelEndpoint.assistants] &&
|
||||
assistantListMap[EModelEndpoint.azureAssistants]
|
||||
) {
|
||||
const spec = getDefaultModelSpec(startupConfig.modelSpecs?.list);
|
||||
newConversation({
|
||||
|
|
@ -108,7 +106,8 @@ export default function ChatRoute() {
|
|||
startupConfig &&
|
||||
!hasSetConversation.current &&
|
||||
!modelsQuery.data?.initial &&
|
||||
assistants
|
||||
assistantListMap[EModelEndpoint.assistants] &&
|
||||
assistantListMap[EModelEndpoint.azureAssistants]
|
||||
) {
|
||||
newConversation({
|
||||
template: initialConvoQuery.data,
|
||||
|
|
@ -120,7 +119,13 @@ export default function ChatRoute() {
|
|||
}
|
||||
/* Creates infinite render if all dependencies included due to newConversation invocations exceeding call stack before hasSetConversation.current becomes truthy */
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [startupConfig, initialConvoQuery.data, endpointsQuery.data, modelsQuery.data, assistants]);
|
||||
}, [
|
||||
startupConfig,
|
||||
initialConvoQuery.data,
|
||||
endpointsQuery.data,
|
||||
modelsQuery.data,
|
||||
assistantListMap,
|
||||
]);
|
||||
|
||||
if (endpointsQuery.isLoading || modelsQuery.isLoading) {
|
||||
return <Spinner className="m-auto text-black dark:text-white" />;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type { TEndpointsConfig } from 'librechat-data-provider';
|
|||
|
||||
const defaultConfig: TEndpointsConfig = {
|
||||
[EModelEndpoint.azureOpenAI]: null,
|
||||
[EModelEndpoint.azureAssistants]: null,
|
||||
[EModelEndpoint.assistants]: null,
|
||||
[EModelEndpoint.openAI]: null,
|
||||
[EModelEndpoint.bingAI]: null,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@ const conversationByIndex = atomFamily<TConversation | null, string | number>({
|
|||
onSet(async (newValue) => {
|
||||
const index = Number(node.key.split('__')[1]);
|
||||
if (newValue?.assistant_id) {
|
||||
localStorage.setItem(`${LocalStorageKeys.ASST_ID_PREFIX}${index}`, newValue.assistant_id);
|
||||
localStorage.setItem(
|
||||
`${LocalStorageKeys.ASST_ID_PREFIX}${index}${newValue?.endpoint}`,
|
||||
newValue.assistant_id,
|
||||
);
|
||||
}
|
||||
if (newValue?.spec) {
|
||||
localStorage.setItem(LocalStorageKeys.LAST_SPEC, newValue.spec);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { parseConvo, EModelEndpoint } from 'librechat-data-provider';
|
||||
import { parseConvo, EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import getLocalStorageItems from './getLocalStorageItems';
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ const buildDefaultConvo = ({
|
|||
};
|
||||
|
||||
// Ensures assistant_id is always defined
|
||||
if (endpoint === EModelEndpoint.assistants && !defaultConvo.assistant_id && convo.assistant_id) {
|
||||
if (isAssistantsEndpoint(endpoint) && !defaultConvo.assistant_id && convo.assistant_id) {
|
||||
defaultConvo.assistant_id = convo.assistant_id;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export default function buildTree({
|
|||
|
||||
if (message.files && fileMap) {
|
||||
messageMap[message.messageId].files = message.files.map(
|
||||
(file) => fileMap[file.file_id] ?? file,
|
||||
(file) => fileMap[file.file_id ?? ''] ?? file,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
defaultEndpoints,
|
||||
modularEndpoints,
|
||||
LocalStorageKeys,
|
||||
isAssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import type {
|
||||
TConfig,
|
||||
|
|
@ -139,8 +140,8 @@ export function getConvoSwitchLogic(params: ConversationInitParams): InitiatedTe
|
|||
};
|
||||
|
||||
const isAssistantSwitch =
|
||||
newEndpoint === EModelEndpoint.assistants &&
|
||||
currentEndpoint === EModelEndpoint.assistants &&
|
||||
isAssistantsEndpoint(newEndpoint) &&
|
||||
isAssistantsEndpoint(currentEndpoint) &&
|
||||
currentEndpoint === newEndpoint;
|
||||
|
||||
const conversationId = conversation?.conversationId;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue