* fix: update new api from cortex to support 0.5.0 Signed-off-by: James <namnh0122@gmail.com> * fix stop button for streaming Signed-off-by: James <namnh0122@gmail.com> * fix stop inference for nonstreaming Signed-off-by: James <namnh0122@gmail.com> * chore: remove umami prevent tracking call to vercel Signed-off-by: James <namnh0122@gmail.com> * add warning modal when running more than 2 model concurrently Signed-off-by: James <namnh0122@gmail.com> * fix: skip summarize if abort Signed-off-by: James <namnh0122@gmail.com> * 0.5.0-3 * add inference error popup Signed-off-by: James <namnh0122@gmail.com> * add back import local model Signed-off-by: James <namnh0122@gmail.com> * fix: max token issue (#3225) Signed-off-by: James <namnh0122@gmail.com> * format status Signed-off-by: James <namnh0122@gmail.com> * fix migration missing instructions Signed-off-by: James <namnh0122@gmail.com> * fix: wait for cortex process overlay should be on top (#3224) * fix: wait for cortex process overlay should be on top * chore: update cortex.js * Cortex 0.5.0-5 * add import model to my model screen Signed-off-by: James <namnh0122@gmail.com> * fix: should migrate symlink models (#3226) * fix import on windows (#3229) Signed-off-by: James <namnh0122@gmail.com> * fix yarn lint Signed-off-by: James <namnh0122@gmail.com> * fix: clean up port before start jan (#3232) Signed-off-by: James <namnh0122@gmail.com> --------- Signed-off-by: James <namnh0122@gmail.com> Co-authored-by: Van Pham <64197333+Van-QA@users.noreply.github.com> Co-authored-by: Louis <louis@jan.ai>
204 lines
6.7 KiB
TypeScript
204 lines
6.7 KiB
TypeScript
import { memo, useCallback, useMemo, useState } from 'react'
|
|
|
|
import { LocalEngines, Model } from '@janhq/core'
|
|
import { Badge, Button, useClickOutside } from '@janhq/joi'
|
|
|
|
import { useAtomValue, useSetAtom } from 'jotai'
|
|
import {
|
|
MoreVerticalIcon,
|
|
PlayIcon,
|
|
StopCircleIcon,
|
|
Trash2Icon,
|
|
} from 'lucide-react'
|
|
import { twMerge } from 'tailwind-merge'
|
|
|
|
import useModelStart from '@/hooks/useModelStart'
|
|
import useModelStop from '@/hooks/useModelStop'
|
|
import useModels from '@/hooks/useModels'
|
|
|
|
import { showWarningMultipleModelModalAtom } from '@/screens/HubScreen2/components/WarningMultipleModelModal'
|
|
|
|
import { activeModelsAtom } from '@/helpers/atoms/Model.atom'
|
|
|
|
type Props = {
|
|
model: Model
|
|
}
|
|
|
|
// If more than this number of models are running, show a warning modal.
|
|
export const concurrentModelWarningThreshold = 2
|
|
|
|
const ModelItem: React.FC<Props> = ({ model }) => {
|
|
const activeModels = useAtomValue(activeModelsAtom)
|
|
const startModel = useModelStart()
|
|
const stopModel = useModelStop()
|
|
const [more, setMore] = useState(false)
|
|
const { deleteModel } = useModels()
|
|
|
|
const [menu, setMenu] = useState<HTMLDivElement | null>(null)
|
|
const [toggle, setToggle] = useState<HTMLDivElement | null>(null)
|
|
const setShowWarningMultipleModelModal = useSetAtom(
|
|
showWarningMultipleModelModalAtom
|
|
)
|
|
useClickOutside(() => setMore(false), null, [menu, toggle])
|
|
|
|
const isActive = useMemo(
|
|
() => activeModels.map((m) => m.model).includes(model.model),
|
|
[activeModels, model.model]
|
|
)
|
|
|
|
const onModelActionClick = useCallback(
|
|
(modelId: string) => {
|
|
if (isActive) {
|
|
// if model already active, stop it
|
|
stopModel.mutate(modelId)
|
|
return
|
|
}
|
|
|
|
if (activeModels.length >= concurrentModelWarningThreshold) {
|
|
// if max concurrent models reached, stop the first model
|
|
// display popup
|
|
setShowWarningMultipleModelModal(true)
|
|
}
|
|
startModel.mutate(modelId)
|
|
},
|
|
[
|
|
isActive,
|
|
startModel,
|
|
stopModel,
|
|
activeModels.length,
|
|
setShowWarningMultipleModelModal,
|
|
]
|
|
)
|
|
|
|
const onDeleteModelClicked = useCallback(
|
|
async (modelId: string) => {
|
|
await stopModel.mutateAsync(modelId)
|
|
await deleteModel(modelId)
|
|
},
|
|
[stopModel, deleteModel]
|
|
)
|
|
|
|
const isLocalModel = LocalEngines.find(
|
|
(e) => model.engine != null && e === model.engine
|
|
)
|
|
|
|
return (
|
|
<div className="border border-b-0 border-[hsla(var(--app-border))] bg-[hsla(var(--tertiary-bg))] p-4 first:rounded-t-lg last:rounded-b-lg last:border-b">
|
|
<div className="flex flex-col items-start justify-start gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
<div className="flex w-1/2 gap-x-8">
|
|
<div className="flex w-full items-center justify-between">
|
|
<h6
|
|
className={twMerge(
|
|
'line-clamp-1 max-w-[200px] font-medium',
|
|
model.engine !== 'cortex.llamacpp' &&
|
|
'max-w-none text-[hsla(var(--text-secondary))]'
|
|
)}
|
|
title={model.model}
|
|
>
|
|
{model.model}
|
|
</h6>
|
|
{model.engine === 'cortex.llamacpp' && (
|
|
<div className="flex gap-x-8">
|
|
<p
|
|
className="line-clamp-1 max-w-[120px] text-[hsla(var(--text-secondary))] xl:max-w-none"
|
|
title={model.model}
|
|
>
|
|
{model.model}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{isLocalModel && (
|
|
<div className="flex gap-x-4">
|
|
<Badge theme="secondary" className="sm:mr-16">
|
|
{model.version != null ? `v${model.version}` : '-'}
|
|
</Badge>
|
|
|
|
<div className="relative flex items-center gap-x-4">
|
|
{isActive ? (
|
|
<Badge
|
|
theme="success"
|
|
variant="soft"
|
|
className="inline-flex items-center space-x-2"
|
|
>
|
|
<span className="h-2 w-2 rounded-full bg-green-500" />
|
|
<span>Active</span>
|
|
</Badge>
|
|
) : (
|
|
<Badge
|
|
theme="secondary"
|
|
className="inline-flex items-center space-x-2"
|
|
>
|
|
<span className="h-2 w-2 rounded-full bg-gray-500" />
|
|
<span>Inactive</span>
|
|
</Badge>
|
|
)}
|
|
<div
|
|
className="inline-flex cursor-pointer"
|
|
ref={setToggle}
|
|
onClick={() => {
|
|
setMore(!more)
|
|
}}
|
|
>
|
|
<Button theme="icon">
|
|
<MoreVerticalIcon />
|
|
</Button>
|
|
{more && (
|
|
<div
|
|
className="shadow-lg absolute right-8 top-0 z-20 w-52 overflow-hidden rounded-lg border border-[hsla(var(--app-border))] bg-[hsla(var(--app-bg))]"
|
|
ref={setMenu}
|
|
>
|
|
<div
|
|
className={twMerge(
|
|
'flex items-center space-x-2 px-4 py-2 hover:bg-[hsla(var(--dropdown-menu-hover-bg))]'
|
|
)}
|
|
onClick={() => {
|
|
onModelActionClick(model.model)
|
|
setMore(false)
|
|
}}
|
|
>
|
|
{isActive ? (
|
|
<StopCircleIcon
|
|
size={16}
|
|
className="text-[hsla(var(--text-secondary))]"
|
|
/>
|
|
) : (
|
|
<PlayIcon
|
|
size={16}
|
|
className="text-[hsla(var(--text-secondary))]"
|
|
/>
|
|
)}
|
|
<span className="text-bold capitalize">
|
|
{isActive ? 'Stop' : 'Start'}
|
|
Model
|
|
</span>
|
|
</div>
|
|
<div
|
|
className={twMerge(
|
|
'flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-[hsla(var(--dropdown-menu-hover-bg))]'
|
|
)}
|
|
onClick={() => onDeleteModelClicked(model.model)}
|
|
>
|
|
<Trash2Icon
|
|
size={16}
|
|
className="text-[hsla(var(--destructive-bg))]"
|
|
/>
|
|
<span className="text-bold text-[hsla(var(--destructive-bg))]">
|
|
Delete Model
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default memo(ModelItem)
|