Add tooltip on every action start and stop button while server is running
This commit is contained in:
parent
8b9d8e8301
commit
056cc8e722
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
35
web/screens/LocalServer/Logs.tsx
Normal file
35
web/screens/LocalServer/Logs.tsx
Normal 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
|
||||
@ -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 */}
|
||||
|
||||
@ -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'}
|
||||
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'}
|
||||
Model
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<div
|
||||
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary"
|
||||
onClick={() => {
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user