Skip to content

Commit

Permalink
fix(Model): switch model caused app crash (janhq#1596)
Browse files Browse the repository at this point in the history
Signed-off-by: James <[email protected]>
Co-authored-by: James <[email protected]>
Co-authored-by: Louis <[email protected]>
  • Loading branch information
3 people authored Jan 17, 2024
1 parent f293e11 commit db987e8
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 127 deletions.
1 change: 0 additions & 1 deletion uikit/src/select/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import * as React from 'react'
import {
CaretSortIcon,
// CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
} from '@radix-ui/react-icons'
Expand Down
143 changes: 17 additions & 126 deletions web/containers/DropdownListSidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect } from 'react'

import {
InferenceEngine,
Model,
ModelRuntimeParams,
ModelSettingParams,
} from '@janhq/core'
import { InferenceEngine, Model } from '@janhq/core'
import {
Button,
Select,
Expand All @@ -14,34 +9,26 @@ import {
SelectItem,
SelectTrigger,
SelectValue,
Input,
Tooltip,
TooltipContent,
TooltipPortal,
TooltipTrigger,
TooltipArrow,
Badge,
} from '@janhq/uikit'

import { atom, useAtomValue, useSetAtom } from 'jotai'
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'

import { MonitorIcon, InfoIcon } from 'lucide-react'
import { MonitorIcon } from 'lucide-react'

import { twMerge } from 'tailwind-merge'

import { MainViewState } from '@/constants/screens'

import { useActiveModel } from '@/hooks/useActiveModel'

import { useEngineSettings } from '@/hooks/useEngineSettings'

import { useMainViewState } from '@/hooks/useMainViewState'

import useRecommendedModel from '@/hooks/useRecommendedModel'

import { toGibibytes } from '@/utils/converter'

import { totalRamAtom, usedRamAtom } from '@/helpers/atoms/SystemBar.atom'
import ModelLabel from '../ModelLabel'

import OpenAiKeyInput from '../OpenAiKeyInput'

import {
ModelParams,
activeThreadAtom,
Expand All @@ -56,25 +43,10 @@ export default function DropdownListSidebar() {
const activeThreadId = useAtomValue(getActiveThreadIdAtom)
const activeThread = useAtomValue(activeThreadAtom)
const threadStates = useAtomValue(threadStatesAtom)
const setSelectedModel = useSetAtom(selectedModelAtom)
const [selectedModel, setSelectedModel] = useAtom(selectedModelAtom)
const setThreadModelParams = useSetAtom(setThreadModelParamsAtom)
const { activeModel } = useActiveModel()

const [selected, setSelected] = useState<Model | undefined>()
const { setMainViewState } = useMainViewState()
const [openAISettings, setOpenAISettings] = useState<
{ api_key: string } | undefined
>(undefined)
const { readOpenAISettings, saveOpenAISettings } = useEngineSettings()
const totalRam = useAtomValue(totalRamAtom)
const usedRam = useAtomValue(usedRamAtom)

useEffect(() => {
readOpenAISettings().then((settings) => {
setOpenAISettings(settings)
})
}, [])

const { recommendedModel, downloadedModels } = useRecommendedModel()

const selectedName =
Expand All @@ -89,7 +61,6 @@ export default function DropdownListSidebar() {
}

useEffect(() => {
setSelected(recommendedModel)
setSelectedModel(recommendedModel)

if (activeThread) {
Expand Down Expand Up @@ -122,7 +93,6 @@ export default function DropdownListSidebar() {
const onValueSelected = useCallback(
(modelId: string) => {
const model = downloadedModels.find((m) => m.id === modelId)
setSelected(model)
setSelectedModel(model)

if (activeThreadId) {
Expand All @@ -140,74 +110,9 @@ export default function DropdownListSidebar() {
return null
}

const getLabel = (size: number) => {
const minimumRamModel = size * 1.25
const availableRam = totalRam - usedRam + (activeModel?.metadata.size ?? 0)
if (minimumRamModel > totalRam) {
return (
<Badge className="space-x-1 rounded-md" themes="danger">
<span>Not enough RAM</span>
<Tooltip>
<TooltipTrigger>
<InfoIcon size={16} />
</TooltipTrigger>
<TooltipPortal>
<TooltipContent
side="right"
sideOffset={10}
className="max-w-[300px]"
>
<span>
{`This tag signals insufficient RAM for optimal model
performance. It's dynamic and may change with your system's
RAM availability.`}
</span>
<TooltipArrow />
</TooltipContent>
</TooltipPortal>
</Tooltip>
</Badge>
)
}
if (minimumRamModel < availableRam) {
return (
<Badge className="space-x-1 rounded-md" themes="success">
<span>Recommended</span>
</Badge>
)
}
if (minimumRamModel < totalRam && minimumRamModel > availableRam) {
return (
<Badge className="space-x-1 rounded-md" themes="warning">
<span>Slow on your device</span>
<Tooltip>
<TooltipTrigger>
<InfoIcon size={16} />
</TooltipTrigger>
<TooltipPortal>
<TooltipContent
side="right"
sideOffset={10}
className="max-w-[300px]"
>
<span>
This tag indicates that your current RAM performance may
affect model speed. It can change based on other active apps.
To improve, consider closing unnecessary applications to free
up RAM.
</span>
<TooltipArrow />
</TooltipContent>
</TooltipPortal>
</Tooltip>
</Badge>
)
}
}

return (
<>
<Select value={selected?.id} onValueChange={onValueSelected}>
<Select value={selectedModel?.id} onValueChange={onValueSelected}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Choose model to start">
{selectedName}
Expand All @@ -229,16 +134,19 @@ export default function DropdownListSidebar() {
<SelectItem
key={i}
value={x.id}
className={twMerge(x.id === selected?.id && 'bg-secondary')}
className={twMerge(
x.id === selectedModel?.id && 'bg-secondary'
)}
>
<div className="flex w-full justify-between">
<span className="line-clamp-1 block">{x.name}</span>
<div className="space-x-2">
<span className="font-bold text-muted-foreground">
{toGibibytes(x.metadata.size)}
</span>
{x.engine == InferenceEngine.nitro &&
getLabel(x.metadata.size)}
{x.engine == InferenceEngine.nitro && (
<ModelLabel size={x.metadata.size} />
)}
</div>
</div>
</SelectItem>
Expand All @@ -258,24 +166,7 @@ export default function DropdownListSidebar() {
</SelectContent>
</Select>

{selected?.engine === InferenceEngine.openai && (
<div className="mt-4">
<label
id="thread-title"
className="mb-2 inline-block font-bold text-gray-600 dark:text-gray-300"
>
API Key
</label>
<Input
id="assistant-instructions"
placeholder="Enter your API_KEY"
defaultValue={openAISettings?.api_key}
onChange={(e) => {
saveOpenAISettings({ apiKey: e.target.value })
}}
/>
</div>
)}
<OpenAiKeyInput selectedModel={selectedModel} />
</>
)
}
34 changes: 34 additions & 0 deletions web/containers/ModelLabel/NotEnoughRamLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react'

import {
Badge,
Tooltip,
TooltipArrow,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from '@janhq/uikit'
import { InfoIcon } from 'lucide-react'

const NotEnoughRamLabel: React.FC = () => (
<Badge className="space-x-1 rounded-md" themes="danger">
<span>Not enough RAM</span>
<Tooltip>
<TooltipTrigger>
<InfoIcon size={16} />
</TooltipTrigger>
<TooltipPortal>
<TooltipContent side="right" sideOffset={10} className="max-w-[300px]">
<span>
{`This tag signals insufficient RAM for optimal model
performance. It's dynamic and may change with your system's
RAM availability.`}
</span>
<TooltipArrow />
</TooltipContent>
</TooltipPortal>
</Tooltip>
</Badge>
)

export default React.memo(NotEnoughRamLabel)
11 changes: 11 additions & 0 deletions web/containers/ModelLabel/RecommendedLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react'

import { Badge } from '@janhq/uikit'

const RecommendedLabel: React.FC = () => (
<Badge className="space-x-1 rounded-md" themes="success">
<span>Recommended</span>
</Badge>
)

export default React.memo(RecommendedLabel)
34 changes: 34 additions & 0 deletions web/containers/ModelLabel/SlowOnYourDeviceLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react'

import {
Badge,
Tooltip,
TooltipArrow,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from '@janhq/uikit'
import { InfoIcon } from 'lucide-react'

const SlowOnYourDeviceLabel: React.FC = () => (
<Badge className="space-x-1 rounded-md" themes="warning">
<span>Slow on your device</span>
<Tooltip>
<TooltipTrigger>
<InfoIcon size={16} />
</TooltipTrigger>
<TooltipPortal>
<TooltipContent side="right" sideOffset={10} className="max-w-[300px]">
<span>
This tag indicates that your current RAM performance may affect
model speed. It can change based on other active apps. To improve,
consider closing unnecessary applications to free up RAM.
</span>
<TooltipArrow />
</TooltipContent>
</TooltipPortal>
</Tooltip>
</Badge>
)

export default React.memo(SlowOnYourDeviceLabel)
43 changes: 43 additions & 0 deletions web/containers/ModelLabel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'

import { useAtomValue } from 'jotai'

import { useActiveModel } from '@/hooks/useActiveModel'

import NotEnoughRamLabel from './NotEnoughRamLabel'

import RecommendedLabel from './RecommendedLabel'

import SlowOnYourDeviceLabel from './SlowOnYourDeviceLabel'

import { totalRamAtom, usedRamAtom } from '@/helpers/atoms/SystemBar.atom'

type Props = {
size: number
}

const ModelLabel: React.FC<Props> = ({ size }) => {
const { activeModel } = useActiveModel()
const totalRam = useAtomValue(totalRamAtom)
const usedRam = useAtomValue(usedRamAtom)

const getLabel = (size: number) => {
const minimumRamModel = size * 1.25
const availableRam = totalRam - usedRam + (activeModel?.metadata.size ?? 0)
if (minimumRamModel > totalRam) {
return <NotEnoughRamLabel />
}
if (minimumRamModel < availableRam) {
return <RecommendedLabel />
}
if (minimumRamModel < totalRam && minimumRamModel > availableRam) {
return <SlowOnYourDeviceLabel />
}

return null
}

return getLabel(size)
}

export default React.memo(ModelLabel)
Loading

0 comments on commit db987e8

Please sign in to comment.