Create and explore custom AI agents with tailored instructions and
diverse skills.
@@ -406,11 +420,10 @@ function AgentForm({ mode }: AgentFormProps) {
setEditing(false)}
- size="sm"
+ rounded="lg"
+ size="xs"
variant="outline"
>
{t('common.cancel')}
{t('common.send')}
diff --git a/apps/shinkai-desktop/src/components/chat/conversation-footer.tsx b/apps/shinkai-desktop/src/components/chat/conversation-footer.tsx
index e8160db6a..987698a43 100644
--- a/apps/shinkai-desktop/src/components/chat/conversation-footer.tsx
+++ b/apps/shinkai-desktop/src/components/chat/conversation-footer.tsx
@@ -11,10 +11,6 @@ import {
ChatMessageFormSchema,
chatMessageFormSchema,
} from '@shinkai_network/shinkai-node-state/forms/chat/chat-message';
-import {
- CreateJobFormSchema,
- createJobFormSchema,
-} from '@shinkai_network/shinkai-node-state/forms/chat/create-job';
import { useSendMessageToInbox } from '@shinkai_network/shinkai-node-state/lib/mutations/sendMesssageToInbox/useSendMessageToInbox';
import { Models } from '@shinkai_network/shinkai-node-state/lib/utils/models';
import { DEFAULT_CHAT_CONFIG } from '@shinkai_network/shinkai-node-state/v2/constants';
@@ -22,8 +18,6 @@ import { useCreateJob } from '@shinkai_network/shinkai-node-state/v2/mutations/c
import { useSendMessageToJob } from '@shinkai_network/shinkai-node-state/v2/mutations/sendMessageToJob/useSendMessageToJob';
import { useStopGeneratingLLM } from '@shinkai_network/shinkai-node-state/v2/mutations/stopGeneratingLLM/useStopGeneratingLLM';
import { useGetChatConfig } from '@shinkai_network/shinkai-node-state/v2/queries/getChatConfig/useGetChatConfig';
-import { useGetChatConversationWithPagination } from '@shinkai_network/shinkai-node-state/v2/queries/getChatConversation/useGetChatConversationWithPagination';
-import { useGetLLMProviders } from '@shinkai_network/shinkai-node-state/v2/queries/getLLMProviders/useGetLLMProviders';
import { useGetSearchTools } from '@shinkai_network/shinkai-node-state/v2/queries/getToolsSearch/useGetToolsSearch';
import {
Button,
@@ -47,12 +41,13 @@ import {
import { formatText, getFileExt } from '@shinkai_network/shinkai-ui/helpers';
import { useDebounce } from '@shinkai_network/shinkai-ui/hooks';
import { cn } from '@shinkai_network/shinkai-ui/utils';
+import equal from 'fast-deep-equal';
import { partial } from 'filesize';
import { AnimatePresence, motion } from 'framer-motion';
-import { Paperclip, X, XIcon } from 'lucide-react';
-import { useCallback, useEffect, useMemo, useRef } from 'react';
+import { Loader2, Paperclip, X, XIcon } from 'lucide-react';
+import { memo, useCallback, useEffect, useRef } from 'react';
import { useDropzone } from 'react-dropzone';
-import { useForm, useWatch } from 'react-hook-form';
+import { useForm } from 'react-hook-form';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { toast } from 'sonner';
@@ -91,88 +86,19 @@ export type ChatConversationLocationState = {
llmProviderId: string;
};
-function ConversationEmptyFooter() {
- const { t } = useTranslation();
- const navigate = useNavigate();
- const { inboxId: encodedInboxId = '' } = useParams();
- const inboxId = decodeURIComponent(encodedInboxId);
- const textareaRef = useRef
(null);
-
- const onSelectedKeysChange = useSetJobScope(
- (state) => state.onSelectedKeysChange,
- );
-
+const useSelectedFilesChat = ({ inboxId }: { inboxId?: string }) => {
const selectedFileKeysRef = useSetJobScope(
(state) => state.selectedFileKeysRef,
);
const selectedFolderKeysRef = useSetJobScope(
(state) => state.selectedFolderKeysRef,
);
- const promptSelected = usePromptSelectionStore(
- (state) => state.promptSelected,
+ const onSelectedKeysChange = useSetJobScope(
+ (state) => state.onSelectedKeysChange,
);
- const auth = useAuth((state) => state.auth);
- const { captureAnalyticEvent } = useAnalytics();
-
const location = useLocation();
-
const locationState = location.state as ChatConversationLocationState;
- const isAgentInbox = locationState?.agentName;
- const defaulAgentId = useSettings((state) => state.defaultAgentId);
-
- const chatForm = useForm({
- resolver: zodResolver(createJobFormSchema),
- defaultValues: {
- message: '',
- files: [],
- },
- });
- const selectedTool = chatForm.watch('tool');
-
- const chatConfigForm = useForm({
- resolver: zodResolver(chatConfigFormSchema),
- defaultValues: {
- stream: DEFAULT_CHAT_CONFIG.stream,
- customPrompt: '',
- temperature: DEFAULT_CHAT_CONFIG.temperature,
- topP: DEFAULT_CHAT_CONFIG.top_p,
- topK: DEFAULT_CHAT_CONFIG.top_k,
- useTools: DEFAULT_CHAT_CONFIG.use_tools,
- },
- });
-
- const { llmProviders } = useGetLLMProviders({
- nodeAddress: auth?.node_address ?? '',
- token: auth?.api_v2_key ?? '',
- });
-
- useEffect(() => {
- if (llmProviders?.length && !defaulAgentId) {
- chatForm.setValue('agent', llmProviders?.[0].id);
- } else {
- chatForm.setValue('agent', defaulAgentId);
- }
- }, [llmProviders, chatForm, defaulAgentId]);
-
- useEffect(() => {
- if (!locationState?.llmProviderId) {
- return;
- }
- const llmProvider = llmProviders.find(
- (agent) => agent.id === locationState.llmProviderId,
- );
- if (llmProvider) {
- chatForm.setValue('agent', llmProvider.id);
- }
- }, [chatForm, locationState, llmProviders]);
-
- useEffect(() => {
- if (!locationState?.agentName) {
- return;
- }
- chatForm.setValue('agent', locationState.agentName);
- }, [chatForm, locationState, llmProviders]);
useEffect(() => {
if (
@@ -215,356 +141,73 @@ function ConversationEmptyFooter() {
selectedFolderKeysRef,
onSelectedKeysChange,
]);
-
- const currentMessage = useWatch({
- control: chatForm.control,
- name: 'message',
- });
- const debounceMessage = useDebounce(currentMessage, 500);
-
- const { data: searchToolList, isSuccess: isSearchToolListSuccess } =
- useGetSearchTools(
- {
- nodeAddress: auth?.node_address ?? '',
- token: auth?.api_v2_key ?? '',
- search: debounceMessage,
- },
- {
- enabled: !!debounceMessage && !!currentMessage && !selectedTool,
- select: (data) => data.slice(0, 3),
- },
- );
-
- const onDrop = useCallback(
- (acceptedFiles: File[]) => {
- const previousFiles = chatForm.getValues('files') ?? [];
- const newFiles = [...previousFiles, ...acceptedFiles];
- chatForm.setValue('files', newFiles, { shouldValidate: true });
- },
- [chatForm],
- );
-
- const {
- getRootProps: getRootFileProps,
- getInputProps: getInputFileProps,
- isDragActive,
- open: openFilePicker,
- } = useDropzone({
- noClick: true,
- noKeyboard: true,
- multiple: true,
- onDrop,
- });
-
- const currentFiles = useWatch({
- control: chatForm.control,
- name: 'files',
- });
- const { mutateAsync: createJob, isPending } = useCreateJob({
- onError: (error) => {
- toast.error('Failed to send message', {
- description: error.response?.data?.message ?? error.message,
- });
- },
- onSuccess: async (data, variables) => {
- navigate(
- `/inboxes/${encodeURIComponent(buildInboxIdFromJobId(data.jobId))}`,
- );
-
- const files = variables?.files ?? [];
- const localFilesCount = (variables.selectedVRFiles ?? [])?.length;
- const localFoldersCount = (variables.selectedVRFolders ?? [])?.length;
-
- if (localFilesCount > 0 || localFoldersCount > 0) {
- captureAnalyticEvent('Ask Local Files', {
- foldersCount: localFoldersCount,
- filesCount: localFilesCount,
- });
- }
- if (files?.length > 0) {
- captureAnalyticEvent('AI Chat with Files', {
- filesCount: files.length,
- });
- } else {
- captureAnalyticEvent('AI Chat', undefined);
- }
+ return {
+ selectedFileKeysRef,
+ selectedFolderKeysRef,
+ clearSelectedFiles: () => {
+ onSelectedKeysChange(null);
+ selectedFileKeysRef.clear();
+ selectedFolderKeysRef.clear();
},
- });
-
- useEffect(() => {
- chatForm.setValue('message', promptSelected?.prompt ?? '');
- }, [chatForm, promptSelected]);
-
- useEffect(() => {
- if (!textareaRef.current) return;
- textareaRef.current.scrollTop = textareaRef.current.scrollHeight;
- textareaRef.current.focus();
- }, [chatForm.watch('message')]);
-
- useEffect(() => {
- chatConfigForm.setValue(
- 'useTools',
- promptSelected?.useTools ? true : DEFAULT_CHAT_CONFIG.use_tools,
- );
- }, [chatConfigForm, chatForm, promptSelected]);
-
- const onSubmit = async (data: CreateJobFormSchema) => {
- if (!auth || data.message.trim() === '') return;
- const selectedVRFiles =
- selectedFileKeysRef.size > 0
- ? Array.from(selectedFileKeysRef.values())
- : [];
- const selectedVRFolders =
- selectedFolderKeysRef.size > 0
- ? Array.from(selectedFolderKeysRef.values())
- : [];
-
- await createJob({
- nodeAddress: auth.node_address,
- token: auth.api_v2_key,
- llmProvider: data.agent,
- content: data.message,
- files: currentFiles,
- isHidden: false,
- toolKey: data.tool?.key,
- selectedVRFiles,
- selectedVRFolders,
- ...(!isAgentInbox && {
- chatConfig: {
- stream: chatConfigForm.getValues('stream'),
- custom_prompt: chatConfigForm.getValues('customPrompt') ?? '',
- temperature: chatConfigForm.getValues('temperature'),
- top_p: chatConfigForm.getValues('topP'),
- top_k: chatConfigForm.getValues('topK'),
- use_tools: chatConfigForm.getValues('useTools'),
- },
- }),
- });
-
- chatForm.reset();
- selectedFileKeysRef.clear();
- selectedFolderKeysRef.clear();
- onSelectedKeysChange(null);
};
+};
- return (
-
-
-
- )}
- />
-
-
- );
-}
-function ConversationChatFooter({ inboxId }: { inboxId: string }) {
+function ConversationChatFooter({
+ inboxId,
+ isLoadingMessage,
+}: {
+ inboxId: string;
+ isLoadingMessage: boolean;
+}) {
const { t } = useTranslation();
+ const navigate = useNavigate();
const textareaRef = useRef