diff --git a/web-app/src/containers/AvatarEmoji.tsx b/web-app/src/containers/AvatarEmoji.tsx index d18bc3a0b..65a4bcef0 100644 --- a/web-app/src/containers/AvatarEmoji.tsx +++ b/web-app/src/containers/AvatarEmoji.tsx @@ -19,7 +19,6 @@ interface AvatarEmojiProps { export const AvatarEmoji: React.FC = ({ avatar, - fallback = '👋', imageClassName = 'w-5 h-5 object-contain', textClassName = 'text-base', }) => { @@ -27,5 +26,5 @@ export const AvatarEmoji: React.FC = ({ return Custom avatar } - return {avatar || fallback} + return {avatar} } diff --git a/web-app/src/containers/dialogs/AddEditAssistant.tsx b/web-app/src/containers/dialogs/AddEditAssistant.tsx index 903ce06f9..7b99bb686 100644 --- a/web-app/src/containers/dialogs/AddEditAssistant.tsx +++ b/web-app/src/containers/dialogs/AddEditAssistant.tsx @@ -29,7 +29,7 @@ import { useTheme } from '@/hooks/useTheme' import { teamEmoji } from '@/utils/teamEmoji' import { AvatarEmoji } from '@/containers/AvatarEmoji' import { useTranslation } from 'react-i18next' -import { cn } from '@/lib/utils' +import { cn, isDev } from '@/lib/utils' interface AddEditAssistantProps { open: boolean @@ -235,12 +235,10 @@ export default function AddEditAssistant({ > - } imageClassName="w-5 h-5 object-contain" textClassName="" /> +
{ diff --git a/web-app/src/hooks/useAppearance.ts b/web-app/src/hooks/useAppearance.ts index 80d4863eb..bca1ec0e5 100644 --- a/web-app/src/hooks/useAppearance.ts +++ b/web-app/src/hooks/useAppearance.ts @@ -54,11 +54,11 @@ const defaultAppPrimaryBgColor: RgbaColor = { r: 219, g: 88, b: 44, a: 1 } const defaultLightAppPrimaryBgColor: RgbaColor = { r: 219, g: 88, b: 44, a: 1 } const defaultAppAccentBgColor: RgbaColor = { r: 45, g: 120, b: 220, a: 1 } const defaultLightAppAccentBgColor: RgbaColor = { r: 45, g: 120, b: 220, a: 1 } -const defaultAppDestructiveBgColor: RgbaColor = { r: 220, g: 45, b: 45, a: 1 } +const defaultAppDestructiveBgColor: RgbaColor = { r: 144, g: 60, b: 60, a: 1 } const defaultLightAppDestructiveBgColor: RgbaColor = { - r: 220, - g: 45, - b: 45, + r: 217, + g: 95, + b: 95, a: 1, } const defaultDarkLeftPanelTextColor: string = '#FFF' diff --git a/web-app/src/hooks/useChat.ts b/web-app/src/hooks/useChat.ts index 86ac50f16..fcda8d0e5 100644 --- a/web-app/src/hooks/useChat.ts +++ b/web-app/src/hooks/useChat.ts @@ -15,7 +15,6 @@ import { newUserThreadContent, postMessageProcessing, sendCompletion, - startModel, } from '@/lib/completion' import { CompletionMessagesBuilder } from '@/lib/messages' import { ChatCompletionMessageToolCall } from 'openai/resources' @@ -25,7 +24,7 @@ import { getTools } from '@/services/mcp' import { MCPTool } from '@/types/completion' import { listen } from '@tauri-apps/api/event' import { SystemEvent } from '@/types/events' -import { stopModel } from '@/services/models' +import { stopModel, startModel } from '@/services/models' export const useChat = () => { const { prompt, setPrompt } = usePrompt() diff --git a/web-app/src/lib/completion.ts b/web-app/src/lib/completion.ts index 972edb660..6546efe00 100644 --- a/web-app/src/lib/completion.ts +++ b/web-app/src/lib/completion.ts @@ -163,39 +163,6 @@ export const isCompletionResponse = ( return 'choices' in response } -/** - * @fileoverview Helper function to start a model. - * This function loads the model from the provider. - * @deprecated This function is deprecated and will be removed in the future. - * Provider's chat function will handle loading the model. - * @param provider - * @param model - * @returns - */ -export const startModel = async ( - provider: ProviderObject, - model: string, - abortController?: AbortController -): Promise => { - const providerObj = EngineManager.instance().get( - normalizeProvider(provider.provider) - ) - const modelObj = provider.models.find((m) => m.id === model) - if (providerObj && modelObj) - return providerObj?.loadModel( - { - id: modelObj.id, - settings: Object.fromEntries( - Object.entries(modelObj.settings ?? {}).map(([key, value]) => [ - key, - value.controller_props?.value, // assuming each setting is { value: ... } - ]) - ), - }, - abortController - ) -} - /** * @fileoverview Helper function to stop a model. * This function unloads the model from the provider. diff --git a/web-app/src/routes/settings/providers/$providerName.tsx b/web-app/src/routes/settings/providers/$providerName.tsx index 95e69f106..9a88d19b7 100644 --- a/web-app/src/routes/settings/providers/$providerName.tsx +++ b/web-app/src/routes/settings/providers/$providerName.tsx @@ -5,7 +5,12 @@ import { useModelProvider } from '@/hooks/useModelProvider' import { cn, getProviderTitle } from '@/lib/utils' import { Switch } from '@/components/ui/switch' import { open } from '@tauri-apps/plugin-dialog' -import { importModel } from '@/services/models' +import { + getActiveModels, + importModel, + startModel, + stopModel, +} from '@/services/models' import { createFileRoute, Link, @@ -27,9 +32,11 @@ import { route } from '@/constants/routes' import DeleteProvider from '@/containers/dialogs/DeleteProvider' import { updateSettings } from '@/services/providers' import { Button } from '@/components/ui/button' -import { IconFolderPlus } from '@tabler/icons-react' +import { IconFolderPlus, IconLoader } from '@tabler/icons-react' import { getProviders } from '@/services/providers' import { toast } from 'sonner' +import { ActiveModel } from '@/types/models' +import { useEffect, useState } from 'react' // as route.threadsDetail export const Route = createFileRoute('/settings/providers/$providerName')({ @@ -67,12 +74,26 @@ const steps = [ function ProviderDetail() { const { step } = useSearch({ from: Route.id }) + const [activeModels, setActiveModels] = useState([]) + const [loadingModels, setLoadingModels] = useState([]) const { providerName } = useParams({ from: Route.id }) const { getProviderByName, setProviders, updateProvider } = useModelProvider() const provider = getProviderByName(providerName) const isSetup = step === 'setup_remote_provider' const navigate = useNavigate() + useEffect(() => { + // Initial data fetch + getActiveModels().then(setActiveModels) + + // Set up interval for real-time updates + const intervalId = setInterval(() => { + getActiveModels().then(setActiveModels) + }, 5000) + + return () => clearInterval(intervalId) + }, [setActiveModels]) + const handleJoyrideCallback = (data: CallBackProps) => { const { status } = data @@ -83,6 +104,38 @@ function ProviderDetail() { } } + const handleStartModel = (modelId: string) => { + // Add model to loading state + setLoadingModels((prev) => [...prev, modelId]) + if (provider) + startModel(provider, modelId) + .then(() => { + setActiveModels((prevModels) => [ + ...prevModels, + { id: modelId } as ActiveModel, + ]) + }) + .catch((error) => { + console.error('Error starting model:', error) + }) + .finally(() => { + // Remove model from loading state + setLoadingModels((prev) => prev.filter((id) => id !== modelId)) + }) + } + + const handleStopModel = (modelId: string) => { + stopModel(modelId) + .then(() => { + setActiveModels((prevModels) => + prevModels.filter((model) => model.id !== modelId) + ) + }) + .catch((error) => { + console.error('Error stopping model:', error) + }) + } + return ( <> + {provider && provider.provider === 'llama.cpp' && ( +
+ {activeModels.some( + (activeModel) => activeModel.id === model.id + ) ? ( + + ) : ( + + )} +
+ )} No model found

- Available models will be listed here. If you don’t have + Available models will be listed here. If you don't have any models yet, visit the  Hub  to download. diff --git a/web-app/src/routes/system-monitor.tsx b/web-app/src/routes/system-monitor.tsx index 3ab35ee2e..fed013ed6 100644 --- a/web-app/src/routes/system-monitor.tsx +++ b/web-app/src/routes/system-monitor.tsx @@ -175,6 +175,7 @@ function SystemMonitor() {