feat: start and stop model (#5133)
* feat: start and stop model * refactor: clean up start models --------- Co-authored-by: Louis <louis@jan.ai>
This commit is contained in:
parent
72d1192499
commit
1b3f16b3e1
@ -19,7 +19,6 @@ interface AvatarEmojiProps {
|
||||
|
||||
export const AvatarEmoji: React.FC<AvatarEmojiProps> = ({
|
||||
avatar,
|
||||
fallback = '👋',
|
||||
imageClassName = 'w-5 h-5 object-contain',
|
||||
textClassName = 'text-base',
|
||||
}) => {
|
||||
@ -27,5 +26,5 @@ export const AvatarEmoji: React.FC<AvatarEmojiProps> = ({
|
||||
return <img src={avatar} alt="Custom avatar" className={imageClassName} />
|
||||
}
|
||||
|
||||
return <span className={textClassName}>{avatar || fallback}</span>
|
||||
return <span className={textClassName}>{avatar}</span>
|
||||
}
|
||||
|
||||
@ -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({
|
||||
>
|
||||
<AvatarEmoji
|
||||
avatar={avatar}
|
||||
fallback={
|
||||
<IconMoodSmile size={18} className="text-main-view-fg/50" />
|
||||
}
|
||||
imageClassName="w-5 h-5 object-contain"
|
||||
textClassName=""
|
||||
/>
|
||||
<IconMoodSmile size={18} className="text-main-view-fg/50" />
|
||||
</div>
|
||||
<div className="relative" ref={emojiPickerRef}>
|
||||
<EmojiPicker
|
||||
@ -248,7 +246,7 @@ export default function AddEditAssistant({
|
||||
theme={isDark ? ('dark' as Theme) : ('light' as Theme)}
|
||||
className="!absolute !z-40 !overflow-y-auto top-2"
|
||||
height={350}
|
||||
customEmojis={teamEmoji}
|
||||
customEmojis={isDev() ? teamEmoji : []}
|
||||
lazyLoadEmojis
|
||||
previewConfig={{ showPreview: false }}
|
||||
onEmojiClick={(emojiData: EmojiClickData) => {
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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<void> => {
|
||||
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.
|
||||
|
||||
@ -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<ActiveModel[]>([])
|
||||
const [loadingModels, setLoadingModels] = useState<string[]>([])
|
||||
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 (
|
||||
<>
|
||||
<Joyride
|
||||
@ -301,6 +354,38 @@ function ProviderDetail() {
|
||||
}
|
||||
actions={
|
||||
<div className="flex items-center gap-1">
|
||||
{provider && provider.provider === 'llama.cpp' && (
|
||||
<div className="mr-1">
|
||||
{activeModels.some(
|
||||
(activeModel) => activeModel.id === model.id
|
||||
) ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={() => handleStopModel(model.id)}
|
||||
>
|
||||
Stop
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={loadingModels.includes(model.id)}
|
||||
onClick={() => handleStartModel(model.id)}
|
||||
>
|
||||
{loadingModels.includes(model.id) ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<IconLoader
|
||||
size={16}
|
||||
className="animate-spin"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
'Start'
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<DialogEditModel
|
||||
provider={provider}
|
||||
modelId={model.id}
|
||||
@ -323,7 +408,7 @@ function ProviderDetail() {
|
||||
<h6 className="font-medium text-base">No model found</h6>
|
||||
</div>
|
||||
<p className="text-left-panel-fg/60 mt-1 text-xs leading-relaxed">
|
||||
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
|
||||
<Link to={route.hub}>Hub</Link>
|
||||
to download.
|
||||
|
||||
@ -175,6 +175,7 @@ function SystemMonitor() {
|
||||
<span className="text-main-view-fg">
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => stopRunningModel(model.id)}
|
||||
>
|
||||
Stop
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ExtensionManager } from '@/lib/extension'
|
||||
import { normalizeProvider } from '@/lib/models'
|
||||
import { EngineManager, ExtensionTypeEnum, ModelExtension } from '@janhq/core'
|
||||
import { Model as CoreModel } from '@janhq/core'
|
||||
|
||||
@ -259,6 +260,38 @@ export const stopModel = async (model: string, provider?: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @fileoverview Helper function to start a model.
|
||||
* This function loads the model from the provider.
|
||||
* 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<void> => {
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the proxy options for model downloads.
|
||||
* @param param0
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user