Add tooltip on every action start and stop button while server is running

This commit is contained in:
Faisal Amir 2024-01-16 16:23:07 +07:00
parent 8b9d8e8301
commit 056cc8e722
9 changed files with 215 additions and 72 deletions

View File

@ -8,7 +8,7 @@ import {
TooltipContent,
TooltipTrigger,
} from '@janhq/uikit'
import { useAtomValue, useSetAtom } from 'jotai'
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { FaGithub, FaDiscord } from 'react-icons/fa'
@ -32,6 +32,8 @@ import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
import useGetSystemResources from '@/hooks/useGetSystemResources'
import { useMainViewState } from '@/hooks/useMainViewState'
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
const menuLinks = [
{
name: 'Discord',
@ -53,6 +55,7 @@ const BottomBar = () => {
const { setMainViewState } = useMainViewState()
const { downloadStates } = useDownloadState()
const setShowSelectModelModal = useSetAtom(showSelectModelModalAtom)
const [serverEnabled] = useAtom(serverEnabledAtom)
return (
<div className="fixed bottom-0 left-16 z-20 flex h-12 w-[calc(100%-64px)] items-center justify-between border-t border-border bg-background/80 px-3">
@ -63,14 +66,16 @@ const BottomBar = () => {
) : null}
</div>
<Badge
themes="secondary"
className="cursor-pointer rounded-md border-none font-medium"
onClick={() => setShowSelectModelModal((show) => !show)}
>
My Models
<ShortCut menu="E" />
</Badge>
{!serverEnabled && (
<Badge
themes="secondary"
className="cursor-pointer rounded-md border-none font-medium"
onClick={() => setShowSelectModelModal((show) => !show)}
>
My Models
<ShortCut menu="E" />
</Badge>
)}
{stateModel.state === 'start' && stateModel.loading && (
<SystemItem

View File

@ -22,10 +22,13 @@ import { useActiveModel } from '@/hooks/useActiveModel'
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
import { useMainViewState } from '@/hooks/useMainViewState'
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
export default function CommandListDownloadedModel() {
const { setMainViewState } = useMainViewState()
const { downloadedModels } = useGetDownloadedModels()
const { activeModel, startModel, stopModel } = useActiveModel()
const [serverEnabled] = useAtom(serverEnabledAtom)
const [showSelectModelModal, setShowSelectModelModal] = useAtom(
showSelectModelModalAtom
)
@ -39,7 +42,7 @@ export default function CommandListDownloadedModel() {
}
const isNotDownloadedModel = downloadedModels.length === 0
if (isNotDownloadedModel) return null
if (isNotDownloadedModel || serverEnabled) return null
return (
<Fragment>

View File

@ -6,6 +6,7 @@ import { toaster } from '@/containers/Toast'
import { useGetDownloadedModels } from './useGetDownloadedModels'
import { LAST_USED_MODEL_ID } from './useRecommendedModel'
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
export const activeModelAtom = atom<Model | undefined>(undefined)

View File

@ -17,7 +17,6 @@
"@hookform/resolvers": "^3.3.2",
"@janhq/core": "link:./core",
"@janhq/uikit": "link:./uikit",
"@janhq/server": "link:./server",
"autoprefixer": "10.4.16",
"class-variance-authority": "^0.7.0",
"framer-motion": "^10.16.4",

View File

@ -2,7 +2,15 @@
import { useCallback, useMemo } from 'react'
import { Model } from '@janhq/core'
import { Badge, Button } from '@janhq/uikit'
import {
Badge,
Button,
Tooltip,
TooltipArrow,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from '@janhq/uikit'
import { atom, useAtomValue } from 'jotai'
@ -23,6 +31,8 @@ import { useMainViewState } from '@/hooks/useMainViewState'
import { toGibibytes } from '@/utils/converter'
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
import { totalRamAtom } from '@/helpers/atoms/SystemBar.atom'
type Props = {
@ -37,6 +47,7 @@ const ExploreModelItemHeader: React.FC<Props> = ({ model, onClick, open }) => {
const { modelDownloadStateAtom, downloadStates } = useDownloadState()
const { requestCreateNewThread } = useCreateNewThread()
const totalRam = useAtomValue(totalRamAtom)
const serverEnabled = useAtomValue(serverEnabledAtom)
const downloadAtom = useMemo(
() => atom((get) => get(modelDownloadStateAtom)[model.id]),
@ -68,13 +79,26 @@ const ExploreModelItemHeader: React.FC<Props> = ({ model, onClick, open }) => {
if (isDownloaded) {
downloadButton = (
<Button
themes="secondaryBlue"
className="min-w-[98px]"
onClick={onUseModelClick}
>
Use
</Button>
<Tooltip>
<TooltipTrigger>
<Button
themes="secondaryBlue"
className="min-w-[98px]"
onClick={onUseModelClick}
disabled={serverEnabled}
>
Use
</Button>
</TooltipTrigger>
{serverEnabled && (
<TooltipPortal>
<TooltipContent side="top">
<span>Threads are disabled while the server is running</span>
<TooltipArrow />
</TooltipContent>
</TooltipPortal>
)}
</Tooltip>
)
}

View File

@ -0,0 +1,35 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { useEffect, useState } from 'react'
import React from 'react'
import { useServerLog } from '@/hooks/useServerLog'
const Logs = () => {
const { getServerLog } = useServerLog()
const [logs, setLogs] = useState([])
useEffect(() => {
getServerLog().then((log) => {
setLogs(log.split(/\r?\n|\r|\n/g))
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [logs])
return (
<div className="p-4">
<code className="text-xs">
{logs.map((log, i) => {
return (
<p key={i} className="my-2 leading-relaxed">
{log}
</p>
)
})}
</code>
</div>
)
}
export default Logs

View File

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { useEffect, useState } from 'react'
'use client'
import React from 'react'
import {
Button,
@ -25,8 +26,11 @@ import { ExternalLinkIcon, InfoIcon } from 'lucide-react'
import { twMerge } from 'tailwind-merge'
import CardSidebar from '@/containers/CardSidebar'
import DropdownListSidebar from '@/containers/DropdownListSidebar'
import DropdownListSidebar, {
selectedModelAtom,
} from '@/containers/DropdownListSidebar'
import { useActiveModel } from '@/hooks/useActiveModel'
import { useServerLog } from '@/hooks/useServerLog'
import { getConfigurationsData } from '@/utils/componentSettings'
@ -37,6 +41,8 @@ import ModelSetting from '../Chat/ModelSetting'
import settingComponentBuilder from '../Chat/ModelSetting/settingComponentBuilder'
import { showRightSideBarAtom } from '../Chat/Sidebar'
import Logs from './Logs'
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
import { getActiveThreadModelParamsAtom } from '@/helpers/atoms/Thread.atom'
@ -49,14 +55,10 @@ const LocalServerScreen = () => {
const modelRuntimeParams = toRuntimeParams(activeModelParams)
const componentDataEngineSetting = getConfigurationsData(modelEngineParams)
const componentDataRuntimeSetting = getConfigurationsData(modelRuntimeParams)
const { getServerLog, openServerLog, clearServerLog } = useServerLog()
const [logs, setLogs] = useState([])
const { openServerLog, clearServerLog } = useServerLog()
const { activeModel, startModel } = useActiveModel()
useEffect(() => {
getServerLog().then((log) => {
setLogs(log.split(/\r?\n|\r|\n/g))
})
}, [getServerLog, logs])
const [selectedModel] = useAtom(selectedModelAtom)
return (
<div className="flex h-full w-full">
@ -78,6 +80,9 @@ const LocalServerScreen = () => {
window.core?.api?.stopServer()
setServerEnabled(false)
} else {
if (!activeModel) {
startModel(String(selectedModel?.id))
}
window.core?.api?.startServer()
setServerEnabled(true)
}
@ -105,7 +110,11 @@ const LocalServerScreen = () => {
</SelectContent>
</Select>
<Input className="w-[60px] flex-shrink-0" value="1337" />
<Input
className="w-[60px] flex-shrink-0"
value="1337"
disabled={serverEnabled}
/>
</div>
<div>
<label
@ -195,17 +204,7 @@ const LocalServerScreen = () => {
</Button>
</div>
</div>
<div className="p-4">
<code className="text-xs">
{logs.map((log, i) => {
return (
<p key={i} className="my-2 leading-relaxed">
{log}
</p>
)
})}
</code>
</div>
<Logs />
</div>
{/* Right bar */}

View File

@ -1,8 +1,16 @@
import { useState } from 'react'
import { InferenceEngine, Model } from '@janhq/core'
import { Badge } from '@janhq/uikit'
import {
Badge,
Tooltip,
TooltipArrow,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from '@janhq/uikit'
import { useAtom } from 'jotai'
import {
MoreVerticalIcon,
Trash2Icon,
@ -10,6 +18,8 @@ import {
StopCircleIcon,
} from 'lucide-react'
import { twMerge } from 'tailwind-merge'
import { useActiveModel } from '@/hooks/useActiveModel'
import { useClickOutside } from '@/hooks/useClickOutside'
@ -17,6 +27,8 @@ import useDeleteModel from '@/hooks/useDeleteModel'
import { toGibibytes } from '@/utils/converter'
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
type RowModelProps = {
data: Model
}
@ -33,6 +45,8 @@ export default function RowModel(props: RowModelProps) {
const isActiveModel = stateModel.model === props.data.id
const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom)
const isRemoteModel =
props.data.engine === InferenceEngine.openai ||
props.data.engine === InferenceEngine.triton_trtllm
@ -40,8 +54,12 @@ export default function RowModel(props: RowModelProps) {
const onModelActionClick = (modelId: string) => {
if (activeModel && activeModel.id === modelId) {
stopModel()
window.core?.api?.stopServer()
setServerEnabled(false)
} else {
startModel(modelId)
if (serverEnabled) {
startModel(modelId)
}
}
}
@ -113,23 +131,49 @@ export default function RowModel(props: RowModelProps) {
className="absolute right-4 top-10 z-20 w-52 overflow-hidden rounded-lg border border-border bg-background py-2 shadow-lg"
ref={setMenu}
>
<div
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary"
onClick={() => {
onModelActionClick(props.data.id)
setMore(false)
}}
>
{activeModel && activeModel.id === props.data.id ? (
<StopCircleIcon size={16} className="text-muted-foreground" />
) : (
<PlayIcon size={16} className="text-muted-foreground" />
<Tooltip>
<TooltipTrigger className="w-full">
<div
className={twMerge(
'flex items-center space-x-2 px-4 py-2 hover:bg-secondary',
serverEnabled &&
activeModel &&
activeModel.id !== props.data.id &&
'pointer-events-none cursor-not-allowed opacity-40'
)}
onClick={() => {
onModelActionClick(props.data.id)
setMore(false)
}}
>
{activeModel && activeModel.id === props.data.id ? (
<StopCircleIcon
size={16}
className="text-muted-foreground"
/>
) : (
<PlayIcon size={16} className="text-muted-foreground" />
)}
<span className="text-bold capitalize text-black dark:text-muted-foreground">
{isActiveModel ? stateModel.state : 'Start'}
&nbsp;Model
</span>
</div>
</TooltipTrigger>
{serverEnabled && (
<TooltipPortal>
<TooltipContent side="top">
<span>
{activeModel && activeModel.id === props.data.id
? 'The API server is running, change model will stop the server'
: 'Threads are disabled while the server is running'}
</span>
<TooltipArrow />
</TooltipContent>
</TooltipPortal>
)}
<span className="text-bold capitalize text-black dark:text-muted-foreground">
{isActiveModel ? stateModel.state : 'Start'}
&nbsp;Model
</span>
</div>
</Tooltip>
<div
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary"
onClick={() => {

View File

@ -1,11 +1,22 @@
import { ScrollArea, Progress, Badge, Button } from '@janhq/uikit'
import {
ScrollArea,
Progress,
Badge,
Button,
Tooltip,
TooltipArrow,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from '@janhq/uikit'
import { useAtomValue } from 'jotai'
import { useAtom, useAtomValue } from 'jotai'
import { useActiveModel } from '@/hooks/useActiveModel'
import { toGibibytes } from '@/utils/converter'
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
import {
cpuUsageAtom,
totalRamAtom,
@ -19,6 +30,7 @@ export default function SystemMonitorScreen() {
const usedRam = useAtomValue(usedRamAtom)
const cpuUsage = useAtomValue(cpuUsageAtom)
const { activeModel, stateModel, stopModel } = useActiveModel()
const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom)
return (
<div className="flex h-full w-full bg-background dark:bg-background">
@ -94,17 +106,38 @@ export default function SystemMonitorScreen() {
<Badge themes="secondary">v{activeModel.version}</Badge>
</td>
<td className="px-6 py-2 text-center">
<Button
block
themes={
stateModel.state === 'stop' ? 'danger' : 'primary'
}
className="w-16"
loading={stateModel.loading}
onClick={() => stopModel()}
>
Stop
</Button>
<Tooltip>
<TooltipTrigger className="w-full">
<Button
block
themes={
stateModel.state === 'stop'
? 'danger'
: 'primary'
}
className="w-16"
loading={stateModel.loading}
onClick={() => {
stopModel()
window.core?.api?.stopServer()
setServerEnabled(false)
}}
>
Stop
</Button>
</TooltipTrigger>
{serverEnabled && (
<TooltipPortal>
<TooltipContent side="top">
<span>
The API server is running, stop the model will
also stop the server
</span>
<TooltipArrow />
</TooltipContent>
</TooltipPortal>
)}
</Tooltip>
</td>
</tr>
</tbody>