mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-23 11:50:14 +01:00
💬 feat: assistant conversation starter (#3699)
* feat: initial UI convoStart
* fix: ConvoStarter UI
* fix: convoStarters bug
* feat: Add input field focus on conversation starters
* style: conversation starter UI update
* feat: apply fixes for starters
* style: update conversationStarters UI and fixed typo
* general UI update
* feat: Add onClick functionality to ConvoStarter component
* fix: quick fix test
* fix(AssistantSelect): remove object check
* fix: updateAssistant `conversation_starters` var
* chore: remove starter autofocus
* fix: no empty conversation starters, always show input, use Constants value for max count
* style: Update defaultTextPropsLabel styles, for a11y placeholder
* refactor: Update ConvoStarter component styles and class names for a11y and theme
* refactor: convostarter, move plus button to within persistent element
* fix: types
* chore: Update landing page assistant description styling with theming
* chore: assistant types
* refactor: documents routes
* refactor: optimize conversation starter mutations/queries
* refactor: Update listAllAssistants return type to Promise<Array<Assistant>>
* feat: edit existing starters
* feat(convo-starters): enhance ConvoStarter component and add animations
- Update ConvoStarter component styling for better visual appeal
- Implement fade-in animation for smoother appearance
- Add hover effect with background color change
- Improve text overflow handling with line-clamp and text-balance
- Ensure responsive design for various screen sizes
* feat(assistant): add conversation starters to assistant builder
- Add localization strings for conversation starters
- Update mobile.css with shake animation for max starters reached
- Enhance user experience with tooltips and dynamic input handling
* refactor: select specific fields for assistant documents fetch
* refactor: remove endpoint query key, fetch all assistant docs for now, add conversation_starters to v1 methods
* refactor: add document filters based on endpoint config
* fix: starters not applied during creation
* refactor: update AssistantSelect component to handle undefined lastSelectedModels
---------
Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
63b80c3067
commit
79f9cd5a4d
58 changed files with 602 additions and 214 deletions
|
|
@ -13,7 +13,7 @@ import type {
|
|||
ValidationResult,
|
||||
AssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import type { ActionAuthForm } from '~/common';
|
||||
import type { ActionAuthForm, ActionWithNullableMetadata } from '~/common';
|
||||
import type { Spec } from './ActionsTable';
|
||||
import { useAssistantsMapContext, useToastContext } from '~/Providers';
|
||||
import { ActionsTable, columns } from './ActionsTable';
|
||||
|
|
@ -37,7 +37,7 @@ export default function ActionsInput({
|
|||
version,
|
||||
setAction,
|
||||
}: {
|
||||
action?: Action;
|
||||
action?: ActionWithNullableMetadata;
|
||||
assistant_id?: string;
|
||||
endpoint: AssistantsEndpoint;
|
||||
version: number | string;
|
||||
|
|
@ -62,12 +62,13 @@ export default function ActionsInput({
|
|||
const [functions, setFunctions] = useState<FunctionTool[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!action?.metadata.raw_spec) {
|
||||
const rawSpec = action?.metadata?.raw_spec ?? '';
|
||||
if (!rawSpec) {
|
||||
return;
|
||||
}
|
||||
setInputValue(action.metadata.raw_spec);
|
||||
debouncedValidation(action.metadata.raw_spec, handleResult);
|
||||
}, [action?.metadata.raw_spec]);
|
||||
setInputValue(rawSpec);
|
||||
debouncedValidation(rawSpec, handleResult);
|
||||
}, [action?.metadata?.raw_spec]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!validationResult || !validationResult.status || !validationResult.spec) {
|
||||
|
|
@ -100,7 +101,8 @@ export default function ActionsInput({
|
|||
},
|
||||
onError(error) {
|
||||
showToast({
|
||||
message: (error as Error).message ?? localize('com_assistants_update_actions_error'),
|
||||
message:
|
||||
(error as Error | undefined)?.message ?? localize('com_assistants_update_actions_error'),
|
||||
status: 'error',
|
||||
});
|
||||
},
|
||||
|
|
@ -108,7 +110,8 @@ export default function ActionsInput({
|
|||
|
||||
const saveAction = handleSubmit((authFormData) => {
|
||||
console.log('authFormData', authFormData);
|
||||
if (!assistant_id) {
|
||||
const currentAssistantId = assistant_id ?? '';
|
||||
if (!currentAssistantId) {
|
||||
// alert user?
|
||||
return;
|
||||
}
|
||||
|
|
@ -121,7 +124,10 @@ export default function ActionsInput({
|
|||
return;
|
||||
}
|
||||
|
||||
let { metadata = {} } = action ?? {};
|
||||
let { metadata } = action ?? {};
|
||||
if (!metadata) {
|
||||
metadata = {};
|
||||
}
|
||||
const action_id = action?.action_id;
|
||||
metadata.raw_spec = inputValue;
|
||||
const parsedUrl = new URL(data[0].domain);
|
||||
|
|
@ -177,10 +183,10 @@ export default function ActionsInput({
|
|||
action_id,
|
||||
metadata,
|
||||
functions,
|
||||
assistant_id,
|
||||
assistant_id: currentAssistantId,
|
||||
endpoint,
|
||||
version,
|
||||
model: assistantMap?.[endpoint][assistant_id].model ?? '',
|
||||
model: assistantMap?.[endpoint][currentAssistantId].model ?? '',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ export default function ActionsPanel({
|
|||
},
|
||||
onError(error) {
|
||||
showToast({
|
||||
message: (error as Error)?.message ?? localize('com_assistants_delete_actions_error'),
|
||||
message:
|
||||
(error as Error | undefined)?.message ?? localize('com_assistants_delete_actions_error'),
|
||||
status: 'error',
|
||||
});
|
||||
},
|
||||
|
|
@ -127,7 +128,7 @@ export default function ActionsPanel({
|
|||
<div className="absolute right-0 top-6">
|
||||
<button
|
||||
type="button"
|
||||
disabled={!assistant_id || !action.action_id}
|
||||
disabled={!(assistant_id ?? '') || !action.action_id}
|
||||
className="btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium"
|
||||
>
|
||||
<TrashIcon className="text-red-500" />
|
||||
|
|
@ -145,16 +146,17 @@ export default function ActionsPanel({
|
|||
}
|
||||
selection={{
|
||||
selectHandler: () => {
|
||||
if (!assistant_id) {
|
||||
const currentId = assistant_id ?? '';
|
||||
if (!currentId) {
|
||||
return showToast({
|
||||
message: 'No assistant_id found, is the assistant created?',
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
deleteAction.mutate({
|
||||
model: assistantMap[endpoint][assistant_id].model,
|
||||
model: assistantMap?.[endpoint][currentId].model ?? '',
|
||||
action_id: action.action_id,
|
||||
assistant_id,
|
||||
assistant_id: currentId,
|
||||
endpoint,
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export default function AssistantAction({
|
|||
{isHovering && (
|
||||
<button
|
||||
type="button"
|
||||
className="transition-color flex h-9 w-9 min-w-9 items-center justify-center rounded-lg duration-200 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
className="transition-colors flex h-9 w-9 min-w-9 items-center justify-center rounded-lg duration-200 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
>
|
||||
<GearIcon className="icon-sm" />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
import React, { useRef, useState } from 'react';
|
||||
import { Plus, X } from 'lucide-react';
|
||||
import { Transition } from 'react-transition-group';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
interface AssistantConversationStartersProps {
|
||||
field: {
|
||||
value: string[];
|
||||
onChange: (value: string[]) => void;
|
||||
};
|
||||
inputClass: string;
|
||||
labelClass: string;
|
||||
}
|
||||
|
||||
const AssistantConversationStarters: React.FC<AssistantConversationStartersProps> = ({
|
||||
field,
|
||||
inputClass,
|
||||
labelClass,
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
|
||||
const nodeRef = useRef(null);
|
||||
const [newStarter, setNewStarter] = useState('');
|
||||
|
||||
const handleAddStarter = () => {
|
||||
if (newStarter.trim() && field.value.length < Constants.MAX_CONVO_STARTERS) {
|
||||
const newValues = [newStarter, ...field.value];
|
||||
field.onChange(newValues);
|
||||
setNewStarter('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteStarter = (index: number) => {
|
||||
const newValues = field.value.filter((_, i) => i !== index);
|
||||
field.onChange(newValues);
|
||||
};
|
||||
const defaultStyle = {
|
||||
transition: 'opacity 200ms ease-in-out',
|
||||
opacity: 0,
|
||||
};
|
||||
|
||||
const triggerShake = (element: HTMLElement) => {
|
||||
element.classList.remove('shake');
|
||||
void element.offsetWidth;
|
||||
element.classList.add('shake');
|
||||
setTimeout(() => {
|
||||
element.classList.remove('shake');
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const transitionStyles = {
|
||||
entering: { opacity: 1 },
|
||||
entered: { opacity: 1 },
|
||||
exiting: { opacity: 0 },
|
||||
exited: { opacity: 0 },
|
||||
};
|
||||
|
||||
const hasReachedMax = field.value.length >= Constants.MAX_CONVO_STARTERS;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<label className={labelClass} htmlFor="conversation_starters">
|
||||
{localize('com_assistants_conversation_starters')}
|
||||
</label>
|
||||
<div className="mt-4 space-y-2">
|
||||
{/* Persistent starter, used for creating only */}
|
||||
<div className="relative">
|
||||
<input
|
||||
ref={(el) => (inputRefs.current[0] = el)}
|
||||
value={newStarter}
|
||||
maxLength={64}
|
||||
className={`${inputClass} pr-10`}
|
||||
type="text"
|
||||
placeholder={
|
||||
hasReachedMax
|
||||
? localize('com_assistants_max_starters_reached')
|
||||
: localize('com_assistants_conversation_starters_placeholder')
|
||||
}
|
||||
onChange={(e) => setNewStarter(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (hasReachedMax) {
|
||||
triggerShake(e.currentTarget);
|
||||
} else {
|
||||
handleAddStarter();
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Transition
|
||||
nodeRef={nodeRef}
|
||||
in={field.value.length < Constants.MAX_CONVO_STARTERS}
|
||||
timeout={200}
|
||||
unmountOnExit
|
||||
>
|
||||
{(state: string) => (
|
||||
<div
|
||||
ref={nodeRef}
|
||||
style={{
|
||||
...defaultStyle,
|
||||
...transitionStyles[state as keyof typeof transitionStyles],
|
||||
transition: state === 'entering' ? 'none' : defaultStyle.transition,
|
||||
}}
|
||||
className="absolute right-1 top-1"
|
||||
>
|
||||
<TooltipProvider delayDuration={1000}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||
onClick={handleAddStarter}
|
||||
disabled={hasReachedMax}
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{hasReachedMax
|
||||
? localize('com_assistants_max_starters_reached')
|
||||
: localize('com_ui_add')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
</div>
|
||||
{field.value.map((starter, index) => (
|
||||
<div key={index} className="relative">
|
||||
<input
|
||||
ref={(el) => (inputRefs.current[index + 1] = el)}
|
||||
value={starter}
|
||||
onChange={(e) => {
|
||||
const newValue = [...field.value];
|
||||
newValue[index] = e.target.value;
|
||||
field.onChange(newValue);
|
||||
}}
|
||||
className={`${inputClass} pr-10`}
|
||||
type="text"
|
||||
maxLength={64}
|
||||
/>
|
||||
<TooltipProvider delayDuration={1000}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute right-1 top-1 flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||
onClick={() => handleDeleteStarter(index)}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{localize('com_ui_delete')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssistantConversationStarters;
|
||||
|
|
@ -14,6 +14,7 @@ import type { FunctionTool, TConfig, TPlugin } from 'librechat-data-provider';
|
|||
import type { AssistantForm, AssistantPanelProps } from '~/common';
|
||||
import { useCreateAssistantMutation, useUpdateAssistantMutation } from '~/data-provider';
|
||||
import { cn, cardStyle, defaultTextProps, removeFocusOutlines } from '~/utils';
|
||||
import AssistantConversationStarters from './AssistantConversationStarters';
|
||||
import { useAssistantsMapContext, useToastContext } from '~/Providers';
|
||||
import { useSelectAssistant, useLocalize } from '~/hooks';
|
||||
import { ToolSelectDialog } from '~/components/Tools';
|
||||
|
|
@ -31,7 +32,7 @@ import { Panel } from '~/common';
|
|||
const labelClass = 'mb-2 text-token-text-primary block font-medium';
|
||||
const inputClass = cn(
|
||||
defaultTextProps,
|
||||
'flex w-full px-3 py-2 dark:border-gray-800 dark:bg-gray-800',
|
||||
'flex w-full px-3 py-2 dark:border-gray-800 dark:bg-gray-800 rounded-xl mb-2',
|
||||
removeFocusOutlines,
|
||||
);
|
||||
|
||||
|
|
@ -106,6 +107,7 @@ export default function AssistantPanel({
|
|||
});
|
||||
},
|
||||
});
|
||||
|
||||
const create = useCreateAssistantMutation({
|
||||
onSuccess: (data) => {
|
||||
setCurrentAssistantId(data.id);
|
||||
|
|
@ -139,7 +141,7 @@ export default function AssistantPanel({
|
|||
return functionName;
|
||||
} else {
|
||||
const assistant = assistantMap?.[endpoint]?.[assistant_id];
|
||||
const tool = assistant?.tools.find((tool) => tool.function?.name === functionName);
|
||||
const tool = assistant?.tools?.find((tool) => tool.function?.name === functionName);
|
||||
if (assistant && tool) {
|
||||
return tool;
|
||||
}
|
||||
|
|
@ -148,7 +150,6 @@ export default function AssistantPanel({
|
|||
return functionName;
|
||||
});
|
||||
|
||||
console.log(data);
|
||||
if (data.code_interpreter) {
|
||||
tools.push({ type: Tools.code_interpreter });
|
||||
}
|
||||
|
|
@ -163,6 +164,7 @@ export default function AssistantPanel({
|
|||
name,
|
||||
description,
|
||||
instructions,
|
||||
conversation_starters: starters,
|
||||
model,
|
||||
// file_ids, // TODO: add file handling here
|
||||
} = data;
|
||||
|
|
@ -174,6 +176,7 @@ export default function AssistantPanel({
|
|||
name,
|
||||
description,
|
||||
instructions,
|
||||
conversation_starters: starters.filter((starter) => starter.trim() !== ''),
|
||||
model,
|
||||
tools,
|
||||
endpoint,
|
||||
|
|
@ -186,6 +189,7 @@ export default function AssistantPanel({
|
|||
name,
|
||||
description,
|
||||
instructions,
|
||||
conversation_starters: starters.filter((starter) => starter.trim() !== ''),
|
||||
model,
|
||||
tools,
|
||||
endpoint,
|
||||
|
|
@ -239,12 +243,12 @@ export default function AssistantPanel({
|
|||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-auto bg-white px-4 pb-8 pt-3 dark:bg-transparent">
|
||||
<div className="bg-surface-50 h-auto px-4 pb-8 pt-3 dark:bg-transparent">
|
||||
{/* Avatar & Name */}
|
||||
<div className="mb-4">
|
||||
<AssistantAvatar
|
||||
createMutation={create}
|
||||
assistant_id={assistant_id ?? null}
|
||||
assistant_id={assistant_id}
|
||||
metadata={assistant['metadata'] ?? null}
|
||||
endpoint={endpoint}
|
||||
version={version}
|
||||
|
|
@ -271,7 +275,7 @@ export default function AssistantPanel({
|
|||
name="id"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<p className="h-3 text-xs italic text-text-secondary">{field.value ?? ''}</p>
|
||||
<p className="h-3 text-xs italic text-text-secondary">{field.value}</p>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -318,6 +322,23 @@ export default function AssistantPanel({
|
|||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Conversation Starters */}
|
||||
<div className="relative mb-6">
|
||||
{/* the label of conversation starters is in the component */}
|
||||
<Controller
|
||||
name="conversation_starters"
|
||||
control={control}
|
||||
defaultValue={[]}
|
||||
render={({ field }) => (
|
||||
<AssistantConversationStarters
|
||||
field={field}
|
||||
inputClass={inputClass}
|
||||
labelClass={labelClass}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{/* Model */}
|
||||
<div className="mb-6">
|
||||
<label className={labelClass} htmlFor="model">
|
||||
|
|
|
|||
|
|
@ -11,7 +11,12 @@ import {
|
|||
} from 'librechat-data-provider';
|
||||
import type { UseFormReset } from 'react-hook-form';
|
||||
import type { UseMutationResult } from '@tanstack/react-query';
|
||||
import type { Assistant, AssistantCreateParams, AssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type {
|
||||
Assistant,
|
||||
AssistantCreateParams,
|
||||
AssistantDocument,
|
||||
AssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import type {
|
||||
Actions,
|
||||
ExtendedFile,
|
||||
|
|
@ -19,13 +24,20 @@ import type {
|
|||
TAssistantOption,
|
||||
LastSelectedModels,
|
||||
} from '~/common';
|
||||
import { useListAssistantsQuery, useGetAssistantDocsQuery } from '~/data-provider';
|
||||
import SelectDropDown from '~/components/ui/SelectDropDown';
|
||||
import { useListAssistantsQuery } from '~/data-provider';
|
||||
import { useLocalize, useLocalStorage } from '~/hooks';
|
||||
import { useFileMapContext } from '~/Providers';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const keys = new Set(['name', 'id', 'description', 'instructions', 'model']);
|
||||
const keys = new Set([
|
||||
'name',
|
||||
'id',
|
||||
'description',
|
||||
'instructions',
|
||||
'conversation_starters',
|
||||
'model',
|
||||
]);
|
||||
|
||||
export default function AssistantSelect({
|
||||
reset,
|
||||
|
|
@ -45,11 +57,18 @@ export default function AssistantSelect({
|
|||
const localize = useLocalize();
|
||||
const fileMap = useFileMapContext();
|
||||
const lastSelectedAssistant = useRef<string | null>(null);
|
||||
const [lastSelectedModels] = useLocalStorage<LastSelectedModels>(
|
||||
const [lastSelectedModels] = useLocalStorage<LastSelectedModels | undefined>(
|
||||
LocalStorageKeys.LAST_MODEL,
|
||||
{} as LastSelectedModels,
|
||||
);
|
||||
|
||||
const { data: documentsMap = new Map<string, AssistantDocument>() } = useGetAssistantDocsQuery(
|
||||
endpoint,
|
||||
{
|
||||
select: (data) => new Map(data.map((dbA) => [dbA.assistant_id, dbA])),
|
||||
},
|
||||
);
|
||||
|
||||
const assistants = useListAssistantsQuery(endpoint, undefined, {
|
||||
select: (res) =>
|
||||
res.data.map((_assistant) => {
|
||||
|
|
@ -57,10 +76,10 @@ export default function AssistantSelect({
|
|||
endpoint === EModelEndpoint.assistants ? FileSources.openai : FileSources.azure;
|
||||
const assistant = {
|
||||
..._assistant,
|
||||
label: _assistant?.name ?? '',
|
||||
label: _assistant.name ?? '',
|
||||
value: _assistant.id,
|
||||
files: _assistant?.file_ids ? ([] as Array<[string, ExtendedFile]>) : undefined,
|
||||
code_files: _assistant?.tool_resources?.code_interpreter?.file_ids
|
||||
files: _assistant.file_ids ? ([] as Array<[string, ExtendedFile]>) : undefined,
|
||||
code_files: _assistant.tool_resources?.code_interpreter?.file_ids
|
||||
? ([] as Array<[string, ExtendedFile]>)
|
||||
: undefined,
|
||||
};
|
||||
|
|
@ -104,11 +123,17 @@ export default function AssistantSelect({
|
|||
}
|
||||
|
||||
if (assistant.code_files && _assistant.tool_resources?.code_interpreter?.file_ids) {
|
||||
_assistant.tool_resources?.code_interpreter?.file_ids?.forEach((file_id) =>
|
||||
_assistant.tool_resources.code_interpreter.file_ids.forEach((file_id) =>
|
||||
handleFile(file_id, assistant.code_files),
|
||||
);
|
||||
}
|
||||
|
||||
const assistantDoc = documentsMap.get(_assistant.id);
|
||||
/* If no user updates, use the latest assistant docs */
|
||||
if (assistantDoc && !assistant.conversation_starters) {
|
||||
assistant.conversation_starters = assistantDoc.conversation_starters;
|
||||
}
|
||||
|
||||
return assistant;
|
||||
}),
|
||||
});
|
||||
|
|
@ -128,8 +153,8 @@ export default function AssistantSelect({
|
|||
|
||||
const update = {
|
||||
...assistant,
|
||||
label: assistant?.name ?? '',
|
||||
value: assistant?.id ?? '',
|
||||
label: assistant.name ?? '',
|
||||
value: assistant.id ?? '',
|
||||
};
|
||||
|
||||
const actions: Actions = {
|
||||
|
|
@ -138,9 +163,9 @@ export default function AssistantSelect({
|
|||
[Capabilities.retrieval]: false,
|
||||
};
|
||||
|
||||
assistant?.tools
|
||||
?.filter((tool) => tool.type !== 'function' || isImageVisionTool(tool))
|
||||
?.map((tool) => tool?.function?.name || tool.type)
|
||||
(assistant.tools ?? [])
|
||||
.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;
|
||||
|
|
@ -148,10 +173,9 @@ export default function AssistantSelect({
|
|||
actions[tool] = true;
|
||||
});
|
||||
|
||||
const functions =
|
||||
assistant?.tools
|
||||
?.filter((tool) => tool.type === 'function' && !isImageVisionTool(tool))
|
||||
?.map((tool) => tool.function?.name ?? '') ?? [];
|
||||
const functions = (assistant.tools ?? [])
|
||||
.filter((tool) => tool.type === 'function' && !isImageVisionTool(tool))
|
||||
.map((tool) => tool.function?.name ?? '');
|
||||
|
||||
const formValues: Partial<AssistantForm & Actions> = {
|
||||
functions,
|
||||
|
|
@ -161,18 +185,26 @@ export default function AssistantSelect({
|
|||
};
|
||||
|
||||
Object.entries(assistant).forEach(([name, value]) => {
|
||||
if (typeof value === 'number') {
|
||||
return;
|
||||
} else if (typeof value === 'object') {
|
||||
if (!keys.has(name)) {
|
||||
return;
|
||||
}
|
||||
if (keys.has(name)) {
|
||||
|
||||
if (
|
||||
name === 'conversation_starters' &&
|
||||
Array.isArray(value) &&
|
||||
value.every((item) => typeof item === 'string')
|
||||
) {
|
||||
formValues[name] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value !== 'number' && typeof value !== 'object') {
|
||||
formValues[name] = value;
|
||||
}
|
||||
});
|
||||
|
||||
reset(formValues);
|
||||
setCurrentAssistantId(assistant?.id);
|
||||
setCurrentAssistantId(assistant.id);
|
||||
},
|
||||
[assistants.data, reset, setCurrentAssistantId, createMutation, endpoint, lastSelectedModels],
|
||||
);
|
||||
|
|
@ -184,7 +216,7 @@ export default function AssistantSelect({
|
|||
return;
|
||||
}
|
||||
|
||||
if (selectedAssistant && assistants.data) {
|
||||
if (selectedAssistant !== '' && selectedAssistant != null && assistants.data) {
|
||||
timerId = setTimeout(() => {
|
||||
lastSelectedAssistant.current = selectedAssistant;
|
||||
onSelect(selectedAssistant);
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export default function AssistantTool({
|
|||
<OGDialogTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="transition-color flex h-9 w-9 min-w-9 items-center justify-center rounded-lg duration-200 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
className="transition-colors flex h-9 w-9 min-w-9 items-center justify-center rounded-lg duration-200 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
|
|
@ -97,7 +97,7 @@ export default function AssistantTool({
|
|||
selection={{
|
||||
selectHandler: () => removeTool(currentTool.pluginKey),
|
||||
selectClasses:
|
||||
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-color duration-200 text-white',
|
||||
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-colors duration-200 text-white',
|
||||
selectText: localize('com_ui_delete'),
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue