diff --git a/web/containers/Layout/BottomBar/index.tsx b/web/containers/Layout/BottomBar/index.tsx index dfd1b324d..ec7a8081b 100644 --- a/web/containers/Layout/BottomBar/index.tsx +++ b/web/containers/Layout/BottomBar/index.tsx @@ -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 (
@@ -63,14 +66,16 @@ const BottomBar = () => { ) : null}
- setShowSelectModelModal((show) => !show)} - > - My Models - - + {!serverEnabled && ( + setShowSelectModelModal((show) => !show)} + > + My Models + + + )} {stateModel.state === 'start' && stateModel.loading && ( diff --git a/web/hooks/useActiveModel.ts b/web/hooks/useActiveModel.ts index 97c04caf0..8c05a515e 100644 --- a/web/hooks/useActiveModel.ts +++ b/web/hooks/useActiveModel.ts @@ -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(undefined) diff --git a/web/package.json b/web/package.json index 5b858c7a7..1300cbf6b 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx index b19f30833..b56e20404 100644 --- a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx @@ -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 = ({ 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 = ({ model, onClick, open }) => { if (isDownloaded) { downloadButton = ( - + + + + + {serverEnabled && ( + + + Threads are disabled while the server is running + + + + )} + ) } diff --git a/web/screens/LocalServer/Logs.tsx b/web/screens/LocalServer/Logs.tsx new file mode 100644 index 000000000..97b625f40 --- /dev/null +++ b/web/screens/LocalServer/Logs.tsx @@ -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 ( +
+ + {logs.map((log, i) => { + return ( +

+ {log} +

+ ) + })} +
+
+ ) +} + +export default Logs diff --git a/web/screens/LocalServer/index.tsx b/web/screens/LocalServer/index.tsx index c841e108e..dd9b7c8ed 100644 --- a/web/screens/LocalServer/index.tsx +++ b/web/screens/LocalServer/index.tsx @@ -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 (
@@ -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 = () => { - +
-
- - {logs.map((log, i) => { - return ( -

- {log} -

- ) - })} -
-
+ {/* Right bar */} diff --git a/web/screens/Settings/Models/Row.tsx b/web/screens/Settings/Models/Row.tsx index 917f4f4b8..6c044c30d 100644 --- a/web/screens/Settings/Models/Row.tsx +++ b/web/screens/Settings/Models/Row.tsx @@ -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} > -
{ - onModelActionClick(props.data.id) - setMore(false) - }} - > - {activeModel && activeModel.id === props.data.id ? ( - - ) : ( - + + +
{ + onModelActionClick(props.data.id) + setMore(false) + }} + > + {activeModel && activeModel.id === props.data.id ? ( + + ) : ( + + )} + + {isActiveModel ? stateModel.state : 'Start'} +  Model + +
+
+ {serverEnabled && ( + + + + {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'} + + + + )} - - {isActiveModel ? stateModel.state : 'Start'} -  Model - -
+ +
{ diff --git a/web/screens/SystemMonitor/index.tsx b/web/screens/SystemMonitor/index.tsx index 675d40eec..941f024f6 100644 --- a/web/screens/SystemMonitor/index.tsx +++ b/web/screens/SystemMonitor/index.tsx @@ -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 (
@@ -94,17 +106,38 @@ export default function SystemMonitorScreen() { v{activeModel.version} - + + + + + {serverEnabled && ( + + + + The API server is running, stop the model will + also stop the server + + + + + )} +