diff --git a/apps/shinkai-desktop/src/pages/add-ai.tsx b/apps/shinkai-desktop/src/pages/add-ai.tsx index 28070598d..6ed7bccd3 100644 --- a/apps/shinkai-desktop/src/pages/add-ai.tsx +++ b/apps/shinkai-desktop/src/pages/add-ai.tsx @@ -14,7 +14,6 @@ import { import { useAddLLMProvider } from '@shinkai_network/shinkai-node-state/v2/mutations/addLLMProvider/useAddLLMProvider'; import { Button, - ErrorMessage, Form, FormControl, FormField, @@ -30,11 +29,13 @@ import { TextField, } from '@shinkai_network/shinkai-ui'; import { cn } from '@shinkai_network/shinkai-ui/utils'; +import { Loader2 } from 'lucide-react'; import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import { toast } from 'sonner'; +import { useURLQueryParams } from '../hooks/use-url-query-params'; import { useAuth } from '../store/auth'; import { SubpageLayout } from './layout/simple-layout'; @@ -99,16 +100,21 @@ const AddAIPage = () => { const { t } = useTranslation(); const auth = useAuth((state) => state.auth); const navigate = useNavigate(); + const query = useURLQueryParams(); + const addAgentForm = useForm({ resolver: zodResolver(addAgentSchema), defaultValues: addAgentFormDefault, }); - const { - mutateAsync: addLLMProvider, - isPending, - isError, - error, - } = useAddLLMProvider({ + + const preSelectedAiProvider = query.get('aiProvider') as Models; + useEffect(() => { + if (preSelectedAiProvider) { + addAgentForm.setValue('model', preSelectedAiProvider); + } + }, [addAgentForm, preSelectedAiProvider]); + + const { mutateAsync: addLLMProvider, isPending } = useAddLLMProvider({ onSuccess: (_, variables) => { navigate('/inboxes', { state: { @@ -122,7 +128,6 @@ const AddAIPage = () => { }); }, }); - const { model: currentModel, isCustomModel: isCustomModelMode, @@ -237,11 +242,37 @@ const AddAIPage = () => { toolkit_permissions: [], model, }, + enableTest: false, + }); + }; + const handleTestAndSave = async (data: AddAgentFormSchema) => { + if (!auth) return; + let model = getModelObject(data.model, data.modelType); + if (isCustomModelMode && data.modelCustom && data.modelTypeCustom) { + model = getModelObject(data.modelCustom, data.modelTypeCustom); + } else if (isCustomModelType && data.modelTypeCustom) { + model = getModelObject(data.model, data.modelTypeCustom); + } + await addLLMProvider({ + nodeAddress: auth?.node_address ?? '', + token: auth?.api_v2_key ?? '', + agent: { + allowed_message_senders: [], + api_key: data.apikey, + external_url: data.externalUrl, + full_identity_name: `${auth.shinkai_identity}/${auth.profile}/agent/${data.agentName}`, + id: data.agentName, + perform_locally: false, + storage_bucket_permissions: [], + toolkit_permissions: [], + model, + }, + enableTest: true, }); }; return ( - +
{ {t('llmProviders.form.modelProvider')} - @@ -426,16 +454,36 @@ const AddAIPage = () => { /> - {isError && } - - + {isPending ? ( +
+ +
+ ) : ( +
+ + +
+ )}
diff --git a/apps/shinkai-desktop/src/pages/ai-model-installation.tsx b/apps/shinkai-desktop/src/pages/ai-model-installation.tsx index ba7dc480b..ef5d8e4ce 100644 --- a/apps/shinkai-desktop/src/pages/ai-model-installation.tsx +++ b/apps/shinkai-desktop/src/pages/ai-model-installation.tsx @@ -1,43 +1,573 @@ import { useTranslation } from '@shinkai_network/shinkai-i18n'; -import { buttonVariants } from '@shinkai_network/shinkai-ui'; +import { Models } from '@shinkai_network/shinkai-node-state/lib/utils/models'; +import { + Button, + buttonVariants, + Card, + CardContent, + CardFooter, + CardHeader, + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from '@shinkai_network/shinkai-ui'; import { cn } from '@shinkai_network/shinkai-ui/utils'; import { QueryClientProvider } from '@tanstack/react-query'; -import { ArrowRight } from 'lucide-react'; -import { Link } from 'react-router-dom'; +import { ArrowRight, Plus, Sparkles } from 'lucide-react'; +import { useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; import { ResourcesBanner } from '../components/hardware-capabilities/resources-banner'; import { OllamaModels } from '../components/shinkai-node-manager/ollama-models'; import { shinkaiNodeQueryClient } from '../lib/shinkai-node-manager/shinkai-node-manager-client'; import { FixedHeaderLayout } from './layout/simple-layout'; -const AIModelInstallation = () => { +const cloudProviders = [ + { + id: Models.OpenAI, + name: 'OpenAI', + icon: ( + + + + ), + models: ['GPT 4o Mini', 'GPT 4o'], + description: + 'OpenAI is an AI research lab that aims to ensure that artificial general intelligence benefits all of humanity.', + }, + { + id: Models.TogetherComputer, + name: 'Together AI', + icon: ( + + together.ai + + + + + + ), + models: [ + 'Meta - LlaMa-2 Chat (70B)', + 'MistralAI - Mistral (7B) Instruct', + 'Teknium - OpenHermes-2-Mistral (7B)', + 'OpenOrca - OpenOrca Mistral (7B) 8K', + ], + }, + { + id: Models.Gemini, + name: 'Gemini', + description: + ' A state-of-the-art large language model offering advanced reasoning, multimodal capabilities, and extended context handling.', + hasInstructions: true, + docUrl: 'https://ai.google.dev/api', + icon: ( + + + + + + + + + + + ), + }, + { + id: Models.Groq, + name: 'Groq', + description: + 'A hardware-accelerated inference solution optimized for low-latency and high-throughput deployments.', + hasInstructions: true, + docUrl: 'https://console.groq.com/docs/overview', + icon: ( + + Groq + + + ), + }, + { + id: Models.OpenRouter, + name: 'OpenRouter', + description: + 'A scalable orchestration layer that intelligently routes queries across multiple AI models, optimizing performance and cost.', + docUrl: 'https://openrouter.ai/docs/quick-start', + icon: ( + + OpenRouter + + + ), + }, + { + id: Models.Exo, + name: 'Exo', + description: + 'A specialized model platform focused on precise information extraction, efficient summarization, and reliable knowledge retrieval.', + icon: ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ), + }, + { + id: Models.Claude, + name: 'Claude', + models: [ + 'Claude 3.5 Sonnet', + 'Claude 3 Opus', + 'Claude 3 Sonnet', + 'Claude 3 Haiku', + ], + docUrl: 'https://docs.anthropic.com/en/docs/intro-to-claude', + icon: ( + + Claude + + + ), + }, +]; +const AIModelInstallation = ({ + isOnboardingStep, +}: { + isOnboardingStep?: boolean; +}) => { const { t } = useTranslation(); + const [showAllOllamaModels, setShowAllOllamaModels] = useState(false); + const navigate = useNavigate(); return ( - - - - - {t('common.continue')} - - + + + + + + + + + + + + + + + Local AI + + + + + + Cloud AI + + } - /> - - + title={t('llmProviders.localAI.installTitle')} + > + + +
+
+

Local AI

+

+ Local AI operates directly on your device, providing immediate + responses and strict data privacy with no internet required. + Ideal for consistent, secure AI access anywhere. +

+
+ +
+ + {isOnboardingStep && ( +
+ + {t('common.continue')} + + +
+ )} +
+ +
+
+

Cloud AI

+

+ Cloud AI leverages remote servers for powerful computational + capabilities, offering scalability and real-time updates. + Access cutting-edge AI features with an active internet + connection. +

+
+ +
+
+ {cloudProviders.map((model) => ( + +
+ +
+
+ {model.icon} +
+

+ {model.name} +

+
+
+ + {model.models ? ( +
+

+ Available Models +

+
    + {model.models.map((item) => ( +
  • + + {item} +
  • + ))} +
+
+ ) : ( +

+ {model.description} +

+ )} + + {model.hasInstructions && ( + + Instructions + + )} +
+
+ + + + + Add AI + + +
+ ))} +
+ {isOnboardingStep && ( +
+ + {t('common.continue')} + + +
+ )} +
+
+
+ ); }; diff --git a/apps/shinkai-desktop/src/pages/ais.tsx b/apps/shinkai-desktop/src/pages/ais.tsx index be68dc8d8..e975e3886 100644 --- a/apps/shinkai-desktop/src/pages/ais.tsx +++ b/apps/shinkai-desktop/src/pages/ais.tsx @@ -68,10 +68,10 @@ const AIsPage = () => { const onAddAgentClick = () => { if (isLocalShinkaiNodeIsUse) { - navigate('/local-ais'); return; } - navigate('/add-ai'); + navigate('/local-ais'); + // navigate('/add-ai'); }; return ( diff --git a/apps/shinkai-desktop/src/pages/layout/simple-layout.tsx b/apps/shinkai-desktop/src/pages/layout/simple-layout.tsx index 6d61cea15..6b15007a8 100644 --- a/apps/shinkai-desktop/src/pages/layout/simple-layout.tsx +++ b/apps/shinkai-desktop/src/pages/layout/simple-layout.tsx @@ -41,24 +41,28 @@ export const FixedHeaderLayout = ({ title, children, className, + rightElement, }: { title: React.ReactNode; children: React.ReactNode; + rightElement?: React.ReactNode; className?: string; }) => { const { t } = useTranslation(); return (
-
- - - {t('common.back')} - -

- {title} -

-
+
+
+ + + {t('common.back')} + +

+ {title} +

+
+ {rightElement}
{children}
diff --git a/apps/shinkai-desktop/src/pages/local-ais.tsx b/apps/shinkai-desktop/src/pages/local-ais.tsx deleted file mode 100644 index 8ebb5e7cb..000000000 --- a/apps/shinkai-desktop/src/pages/local-ais.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { useTranslation } from '@shinkai_network/shinkai-i18n'; -import { Button, buttonVariants } from '@shinkai_network/shinkai-ui'; -import { cn } from '@shinkai_network/shinkai-ui/utils'; -import { QueryClientProvider } from '@tanstack/react-query'; -import { Plus, Sparkles } from 'lucide-react'; -import React from 'react'; -import { Link } from 'react-router-dom'; - -import { OllamaModels } from '../components/shinkai-node-manager/ollama-models'; -import { shinkaiNodeQueryClient } from '../lib/shinkai-node-manager/shinkai-node-manager-client'; -import { SubpageLayout } from './layout/simple-layout'; - -const LocalAisPage = () => { - const { t } = useTranslation(); - const [showAllOllamaModels, setShowAllOllamaModels] = React.useState(false); - return ( - - -

- {t('llmProviders.localAI.installText')} -

- -
- - - {t('llmProviders.addManually')} - - -
-
-
- ); -}; - -export default LocalAisPage; diff --git a/apps/shinkai-desktop/src/routes/index.tsx b/apps/shinkai-desktop/src/routes/index.tsx index 6a2f80960..d51c494b6 100644 --- a/apps/shinkai-desktop/src/routes/index.tsx +++ b/apps/shinkai-desktop/src/routes/index.tsx @@ -50,7 +50,6 @@ import GetStartedPage from '../pages/get-started'; import MainLayout from '../pages/layout/main-layout'; import OnboardingLayout from '../pages/layout/onboarding-layout'; import SettingsLayout from '../pages/layout/settings-layout'; -import LocalAisPage from '../pages/local-ais'; import { PromptLibrary } from '../pages/prompt-library'; import { PublicKeys } from '../pages/public-keys'; import QuickConnectionPage from '../pages/quick-connection'; @@ -203,7 +202,7 @@ const AppRoutes = () => { path={'/free-subscriptions'} /> } + element={} path={'/ai-model-installation'} /> { } > - } path="local-ais" /> + } path="local-ais" /> } path="ais" /> } path="add-ai" /> } path="add-agent" /> diff --git a/libs/shinkai-message-ts/src/api/jobs/index.ts b/libs/shinkai-message-ts/src/api/jobs/index.ts index aad57ca8a..dbc39a112 100644 --- a/libs/shinkai-message-ts/src/api/jobs/index.ts +++ b/libs/shinkai-message-ts/src/api/jobs/index.ts @@ -241,6 +241,21 @@ export const addLLMProvider = async ( ); return response.data as AddLLMProviderResponse; }; +export const testLLMProvider = async ( + nodeAddress: string, + bearerToken: string, + payload: AddLLMProviderRequest, +) => { + const response = await httpClient.post( + urlJoin(nodeAddress, '/v2/test_llm_provider'), + { ...payload, model: getModelString(payload.model) }, + { + headers: { Authorization: `Bearer ${bearerToken}` }, + responseType: 'json', + }, + ); + return response.data as AddLLMProviderResponse; +}; export const updateLLMProvider = async ( nodeAddress: string, diff --git a/libs/shinkai-node-state/src/v2/mutations/addLLMProvider/index.ts b/libs/shinkai-node-state/src/v2/mutations/addLLMProvider/index.ts index 6eda8ebde..2d987c72f 100644 --- a/libs/shinkai-node-state/src/v2/mutations/addLLMProvider/index.ts +++ b/libs/shinkai-node-state/src/v2/mutations/addLLMProvider/index.ts @@ -1,4 +1,7 @@ -import { addLLMProvider as addLLMProviderAPI } from '@shinkai_network/shinkai-message-ts/api/jobs/index'; +import { + addLLMProvider as addLLMProviderAPI, + testLLMProvider, +} from '@shinkai_network/shinkai-message-ts/api/jobs/index'; import { AddLLMProviderInput } from './types'; @@ -6,7 +9,11 @@ export const addLLMProvider = async ({ nodeAddress, token, agent, + enableTest, }: AddLLMProviderInput) => { + if (!agent.model.Ollama && enableTest) { + await testLLMProvider(nodeAddress, token, agent); + } const data = await addLLMProviderAPI(nodeAddress, token, agent); return data; }; diff --git a/libs/shinkai-node-state/src/v2/mutations/addLLMProvider/types.ts b/libs/shinkai-node-state/src/v2/mutations/addLLMProvider/types.ts index 3f256f190..c687fb66e 100644 --- a/libs/shinkai-node-state/src/v2/mutations/addLLMProvider/types.ts +++ b/libs/shinkai-node-state/src/v2/mutations/addLLMProvider/types.ts @@ -7,5 +7,6 @@ import { export type AddLLMProviderInput = Token & { nodeAddress: string; agent: SerializedLLMProvider; + enableTest?: boolean; }; export type AddLLMProviderOutput = AddLLMProviderResponse; diff --git a/libs/shinkai-node-state/src/v2/mutations/testLLMProvider/index.ts b/libs/shinkai-node-state/src/v2/mutations/testLLMProvider/index.ts new file mode 100644 index 000000000..55825fb94 --- /dev/null +++ b/libs/shinkai-node-state/src/v2/mutations/testLLMProvider/index.ts @@ -0,0 +1,19 @@ +import { + addLLMProvider as addLLMProviderAPI, + testLLMProvider, +} from '@shinkai_network/shinkai-message-ts/api/jobs/index'; + +import { AddLLMProviderInput } from './types'; + +export const addLLMProvider = async ({ + nodeAddress, + token, + agent, +}: AddLLMProviderInput) => { + if (!agent.model.Ollama) { + await testLLMProvider(nodeAddress, token, agent); + } + + const data = await addLLMProviderAPI(nodeAddress, token, agent); + return data; +}; diff --git a/libs/shinkai-node-state/src/v2/mutations/testLLMProvider/types.ts b/libs/shinkai-node-state/src/v2/mutations/testLLMProvider/types.ts new file mode 100644 index 000000000..3f256f190 --- /dev/null +++ b/libs/shinkai-node-state/src/v2/mutations/testLLMProvider/types.ts @@ -0,0 +1,11 @@ +import { Token } from '@shinkai_network/shinkai-message-ts/api/general/types'; +import { + AddLLMProviderResponse, + SerializedLLMProvider, +} from '@shinkai_network/shinkai-message-ts/api/jobs/types'; + +export type AddLLMProviderInput = Token & { + nodeAddress: string; + agent: SerializedLLMProvider; +}; +export type AddLLMProviderOutput = AddLLMProviderResponse; diff --git a/libs/shinkai-node-state/src/v2/mutations/testLLMProvider/useAddLLMProvider.ts b/libs/shinkai-node-state/src/v2/mutations/testLLMProvider/useAddLLMProvider.ts new file mode 100644 index 000000000..749108de8 --- /dev/null +++ b/libs/shinkai-node-state/src/v2/mutations/testLLMProvider/useAddLLMProvider.ts @@ -0,0 +1,19 @@ +import type { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; + +import { APIError } from '../../types'; +import { addLLMProvider } from '.'; +import { AddLLMProviderInput, AddLLMProviderOutput } from './types'; + +type Options = UseMutationOptions< + AddLLMProviderOutput, + APIError, + AddLLMProviderInput +>; + +export const useAddLLMProvider = (options?: Options) => { + return useMutation({ + mutationFn: addLLMProvider, + ...options, + }); +};