diff --git a/apps/shinkai-desktop/src/components/chat/chat-action-bar/chat-config-action-bar.tsx b/apps/shinkai-desktop/src/components/chat/chat-action-bar/chat-config-action-bar.tsx index 8f95653ae..13b68af03 100644 --- a/apps/shinkai-desktop/src/components/chat/chat-action-bar/chat-config-action-bar.tsx +++ b/apps/shinkai-desktop/src/components/chat/chat-action-bar/chat-config-action-bar.tsx @@ -4,7 +4,9 @@ import { useTranslation } from '@shinkai_network/shinkai-i18n'; import { extractJobIdFromInbox } from '@shinkai_network/shinkai-message-ts/utils/inbox_name_handler'; import { useUpdateChatConfig } from '@shinkai_network/shinkai-node-state/v2/mutations/updateChatConfig/useUpdateChatConfig'; import { useGetChatConfig } from '@shinkai_network/shinkai-node-state/v2/queries/getChatConfig/useGetChatConfig'; +import { useGetLLMProviders } from '@shinkai_network/shinkai-node-state/v2/queries/getLLMProviders/useGetLLMProviders'; import { + Alert, Button, Form, Popover, @@ -12,6 +14,7 @@ import { PopoverTrigger, Tooltip, TooltipContent, + TooltipPortal, TooltipProvider, TooltipTrigger, } from '@shinkai_network/shinkai-ui'; @@ -28,7 +31,9 @@ import { Switch, Textarea, } from '@shinkai_network/shinkai-ui'; +import { cn } from '@shinkai_network/shinkai-ui/utils'; import { Settings2 } from 'lucide-react'; +import { InfoCircleIcon } from 'primereact/icons/infocircle'; import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { UseFormReturn } from 'react-hook-form'; @@ -37,6 +42,7 @@ import { toast } from 'sonner'; import { z } from 'zod'; import { useAuth } from '../../../store/auth'; +import { useSettings } from '../../../store/settings'; import { actionButtonClassnames } from '../conversation-footer'; export const chatConfigFormSchema = z.object({ @@ -293,70 +299,73 @@ export function UpdateChatConfigActionBar() { }; return ( - { - if (open) { - form.reset({ - stream: chatConfig?.stream, - customPrompt: chatConfig?.custom_prompt ?? '', - temperature: chatConfig?.temperature, - topP: chatConfig?.top_p, - topK: chatConfig?.top_k, - }); - } - }} - > - - - - - - - - -

- Chat Settings -

+
+ + { + if (open) { + form.reset({ + stream: chatConfig?.stream, + customPrompt: chatConfig?.custom_prompt ?? '', + temperature: chatConfig?.temperature, + topP: chatConfig?.top_p, + topK: chatConfig?.top_k, + }); + } + }} + > + + + + + + + + +

+ Chat Settings +

-
- - -
- - - - - - -
- - -
- Chat Settings -
-
-
+
+ + +
+ + + + + + +
+ + + + Chat Settings + + + +
); } export function CreateChatConfigActionBar({ @@ -365,37 +374,88 @@ export function CreateChatConfigActionBar({ form: UseFormReturn; }) { return ( - - - - - - - - - -

- Chat Settings -

+
+ + + + + + + + + + +

+ Chat Settings +

-
- - - - -
- Chat Settings -
-
-
+
+ + + + + + Chat Settings + + + +
); } + +const useSelectedAIModel = () => { + const defaultAgentId = useSettings((state) => state.defaultAgentId); + const auth = useAuth((state) => state.auth); + + const { llmProviders } = useGetLLMProviders({ + nodeAddress: auth?.node_address ?? '', + token: auth?.api_v2_key ?? '', + }); + const selectedProvider = llmProviders?.find( + (provider) => provider.id === defaultAgentId, + ); + return selectedProvider; +}; + +const ToolsDisabledAlert = ({ streamActive }: { streamActive?: boolean }) => { + const selectedAIModel = useSelectedAIModel(); + + const isOllamaProvider = + selectedAIModel?.model?.split(':')?.[0] === 'ollama' && streamActive; + + return isOllamaProvider ? ( + + + + svg]:static [&>svg~*]:pl-0', + 'flex w-full items-center gap-2 rounded-lg px-3 py-1.5', + )} + variant="info" + > + + Tools disabled + + + + +

+ Turn off streaming in chat config to allow tool usage (Ollama + limitation). +

+
+
+
+
+ ) : null; +}; diff --git a/apps/shinkai-desktop/src/components/chat/conversation-footer.tsx b/apps/shinkai-desktop/src/components/chat/conversation-footer.tsx index c7d0f7b8c..cb9009b4f 100644 --- a/apps/shinkai-desktop/src/components/chat/conversation-footer.tsx +++ b/apps/shinkai-desktop/src/components/chat/conversation-footer.tsx @@ -30,7 +30,6 @@ import { useGetChatConversationWithPagination } from '@shinkai_network/shinkai-n import { useGetLLMProviders } from '@shinkai_network/shinkai-node-state/v2/queries/getLLMProviders/useGetLLMProviders'; import { useGetWorkflowSearch } from '@shinkai_network/shinkai-node-state/v2/queries/getWorkflowSearch/useGetWorkflowSearch'; import { - Alert, Button, ChatInputArea, Form, @@ -56,7 +55,6 @@ import { cn } from '@shinkai_network/shinkai-ui/utils'; import { partial } from 'filesize'; import { AnimatePresence, motion } from 'framer-motion'; import { Paperclip, SendIcon, X, XIcon } from 'lucide-react'; -import { InfoCircleIcon } from 'primereact/icons/infocircle'; import { useEffect, useMemo, useState } from 'react'; import { useDropzone } from 'react-dropzone'; import { useForm, useWatch } from 'react-hook-form'; @@ -84,7 +82,7 @@ import { streamingSupportedModels } from './constants'; import { useSetJobScope } from './context/set-job-scope-context'; export const actionButtonClassnames = - 'bg-gray-350 inline-flex h-[30px] w-[30px] cursor-pointer items-center justify-center gap-1.5 truncate border border-gray-200 px-[7px] py-1.5 text-left text-xs rounded-lg font-normal text-gray-50 hover:bg-gray-300 hover:text-white'; + 'shrink-0 bg-gray-350 inline-flex h-[30px] w-[30px] cursor-pointer items-center justify-center gap-1.5 truncate border border-gray-200 px-[7px] py-1.5 text-left text-xs rounded-lg font-normal text-gray-50 hover:bg-gray-300 hover:text-white'; export type ChatConversationLocationState = { files: File[]; agentName: string; @@ -385,10 +383,7 @@ function ConversationEmptyFooter() { -
- - -
+ -
- - -
+ + { - const defaultAgentId = useSettings((state) => state.defaultAgentId); - const auth = useAuth((state) => state.auth); - - const { llmProviders } = useGetLLMProviders({ - nodeAddress: auth?.node_address ?? '', - token: auth?.api_v2_key ?? '', - }); - const selectedProvider = llmProviders?.find( - (provider) => provider.id === defaultAgentId, - ); - return selectedProvider; -}; - -const ToolsDisabledAlert = () => { - const selectedAIModel = useSelectedAIModel(); - - const isOllamaProvider = selectedAIModel?.model?.split(':')?.[0] === 'ollama'; - - return isOllamaProvider ? ( - - - - svg]:static [&>svg~*]:pl-0', - 'flex w-full items-center gap-2 rounded-lg px-3 py-1.5', - )} - variant="info" - > - - Tools disabled - - - - -

- Turn off streaming in chat config to allow tool usage (Ollama - limitation). -

-
-
-
-
- ) : null; -}; diff --git a/apps/shinkai-desktop/src/pages/chat/chat-conversation.tsx b/apps/shinkai-desktop/src/pages/chat/chat-conversation.tsx index e033b9e00..f12b72609 100644 --- a/apps/shinkai-desktop/src/pages/chat/chat-conversation.tsx +++ b/apps/shinkai-desktop/src/pages/chat/chat-conversation.tsx @@ -27,6 +27,7 @@ import { AlertCircle } from 'lucide-react'; import { useEffect, useMemo, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import useWebSocket from 'react-use-websocket'; +import { toast } from 'sonner'; import { streamingSupportedModels } from '../../components/chat/constants'; import ConversationFooter from '../../components/chat/conversation-footer'; @@ -141,6 +142,8 @@ const ChatConversation = () => { isPending: isChatConversationLoading, isFetchingPreviousPage, isSuccess: isChatConversationSuccess, + isError, + error: chatConversationError, } = useGetChatConversationWithPagination({ token: auth?.api_v2_key ?? '', nodeAddress: auth?.node_address ?? '', @@ -151,6 +154,17 @@ const ChatConversation = () => { !hasProviderEnableStreaming || chatConfig?.stream === false, }); + useEffect(() => { + if (isError) { + console.error('Failed loading chat conversation', chatConversationError); + toast.error('Failed loading chat conversation', { + description: + chatConversationError?.response?.data?.message ?? + chatConversationError.message, + }); + } + }, [chatConversationError, isError]); + const { mutateAsync: retryMessage } = useRetryMessage(); const isLoadingMessage = useMemo(() => { diff --git a/libs/shinkai-node-state/src/v2/queries/getChatConversation/index.ts b/libs/shinkai-node-state/src/v2/queries/getChatConversation/index.ts index 3f4797684..d77b98f2c 100644 --- a/libs/shinkai-node-state/src/v2/queries/getChatConversation/index.ts +++ b/libs/shinkai-node-state/src/v2/queries/getChatConversation/index.ts @@ -48,7 +48,7 @@ export const getChatConversation = async ({ ? 'https://ui-avatars.com/api/?name=Me&background=313336&color=b0b0b0' : 'https://ui-avatars.com/api/?name=S&background=FF7E7F&color=ffffff', }, - toolCalls: message.job_message?.metadata?.function_calls.map( + toolCalls: (message.job_message?.metadata?.function_calls ?? []).map( (tool) => ({ name: tool.name, args: tool.arguments, @@ -73,15 +73,20 @@ export const getChatConversation = async ({ preview?: string; } = { name }; if (name.match(/\.(jpg|jpeg|png|gif)$/i)) { - const response = await downloadFileFromInbox( - nodeAddress, - token, - inbox, - name, - ); - if (response) { - const blob = new Blob([response]); - file.preview = URL.createObjectURL(blob); + try { + const response = await downloadFileFromInbox( + nodeAddress, + token, + inbox, + name, + ); + if (response) { + const blob = new Blob([response]); + file.preview = URL.createObjectURL(blob); + } + } catch (error) { + console.error(error); + throw new Error(`Failed to download file - ${name}`); } } return file; diff --git a/libs/shinkai-node-state/src/v2/queries/getChatConversation/useGetChatConversationWithPagination.ts b/libs/shinkai-node-state/src/v2/queries/getChatConversation/useGetChatConversationWithPagination.ts index 081375980..503549f98 100644 --- a/libs/shinkai-node-state/src/v2/queries/getChatConversation/useGetChatConversationWithPagination.ts +++ b/libs/shinkai-node-state/src/v2/queries/getChatConversation/useGetChatConversationWithPagination.ts @@ -2,6 +2,7 @@ import { isJobInbox } from '@shinkai_network/shinkai-message-ts/utils'; import { useInfiniteQuery } from '@tanstack/react-query'; import { FunctionKeyV2 } from '../../constants'; +import { APIError } from '../../types'; import { getChatConversation } from '.'; import { ChatConversationInfiniteData, @@ -17,7 +18,7 @@ export const useGetChatConversationWithPagination = ( ) => { const response = useInfiniteQuery< GetChatConversationOutput, - Error, + APIError, ChatConversationInfiniteData, [string, { inboxId: string }], { lastKey: string | null }