From 43eff865ff98e38bb4ca9babb8575a4272b72cfc Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Tue, 19 Nov 2024 21:42:52 +0700 Subject: [PATCH] enhance ux local server page (#4045) --- web/containers/ServerLogs/index.tsx | 49 ++++++++++++++++--- .../LocalServerCenterPanel/index.tsx | 10 ++-- .../LocalServerLeftPanel/index.tsx | 12 ++++- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/web/containers/ServerLogs/index.tsx b/web/containers/ServerLogs/index.tsx index e12d89fd1..2e978bd23 100644 --- a/web/containers/ServerLogs/index.tsx +++ b/web/containers/ServerLogs/index.tsx @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { memo, useCallback, useEffect, useState } from 'react' +import { memo, useCallback, useEffect, useRef, useState } from 'react' -import { Button, useClipboard } from '@janhq/joi' +import { Button, ScrollArea, useClipboard } from '@janhq/joi' import { useAtomValue } from 'jotai' import { FolderIcon, CheckIcon, CopyIcon } from 'lucide-react' @@ -22,6 +22,9 @@ const ServerLogs = (props: ServerLogsProps) => { const { getLogs } = useLogs() const serverEnabled = useAtomValue(serverEnabledAtom) const [logs, setLogs] = useState([]) + const listRef = useRef(null) + const prevScrollTop = useRef(0) + const isUserManuallyScrollingUp = useRef(false) const updateLogs = useCallback( () => @@ -58,13 +61,45 @@ const ServerLogs = (props: ServerLogsProps) => { const clipboard = useClipboard({ timeout: 1000 }) + const handleScroll = useCallback((event: React.UIEvent) => { + const currentScrollTop = event.currentTarget.scrollTop + + if (prevScrollTop.current > currentScrollTop) { + isUserManuallyScrollingUp.current = true + } else { + const currentScrollTop = event.currentTarget.scrollTop + const scrollHeight = event.currentTarget.scrollHeight + const clientHeight = event.currentTarget.clientHeight + + if (currentScrollTop + clientHeight >= scrollHeight) { + isUserManuallyScrollingUp.current = false + } + } + + if (isUserManuallyScrollingUp.current === true) { + event.preventDefault() + event.stopPropagation() + } + prevScrollTop.current = currentScrollTop + }, []) + + useEffect(() => { + if (isUserManuallyScrollingUp.current === true || !listRef.current) return + const scrollHeight = listRef.current?.scrollHeight ?? 0 + listRef.current?.scrollTo({ + top: scrollHeight, + behavior: 'instant', + }) + }, [listRef.current?.scrollHeight, isUserManuallyScrollingUp, logs]) + return ( -
{withCopy && (
@@ -107,7 +142,7 @@ const ServerLogs = (props: ServerLogsProps) => { )}
{logs.length > 0 ? ( - + {logs.slice(-limit).map((log, i) => { return (

@@ -256,7 +291,7 @@ const ServerLogs = (props: ServerLogsProps) => {

)}
-
+ ) } diff --git a/web/screens/LocalServer/LocalServerCenterPanel/index.tsx b/web/screens/LocalServer/LocalServerCenterPanel/index.tsx index e16ceb329..c5e42a9d2 100644 --- a/web/screens/LocalServer/LocalServerCenterPanel/index.tsx +++ b/web/screens/LocalServer/LocalServerCenterPanel/index.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' -import { Button, ScrollArea } from '@janhq/joi' +import { Button } from '@janhq/joi' import { CodeIcon, Paintbrush } from 'lucide-react' import { InfoIcon } from 'lucide-react' @@ -26,8 +26,8 @@ const LocalServerCenterPanel = () => { return ( -
-
+
+

Server Logs

) : ( - - - + )}
diff --git a/web/screens/LocalServer/LocalServerLeftPanel/index.tsx b/web/screens/LocalServer/LocalServerLeftPanel/index.tsx index 6f5de80ec..91e00b430 100644 --- a/web/screens/LocalServer/LocalServerLeftPanel/index.tsx +++ b/web/screens/LocalServer/LocalServerLeftPanel/index.tsx @@ -29,6 +29,7 @@ const LocalServerLeftPanel = () => { const [errorRangePort, setErrorRangePort] = useState(false) const [errorPrefix, setErrorPrefix] = useState(false) const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom) + const [isLoading, setIsLoading] = useState(false) const { startModel, stateModel } = useActiveModel() const selectedModel = useAtomValue(selectedModelAtom) @@ -66,6 +67,7 @@ const LocalServerLeftPanel = () => { const onStartServerClick = async () => { if (selectedModel == null) return try { + setIsLoading(true) const isStarted = await window.core?.api?.startServer({ host, port, @@ -79,8 +81,10 @@ const LocalServerLeftPanel = () => { setFirstTimeVisitAPIServer(false) } startModel(selectedModel.id, false).catch((e) => console.error(e)) + setIsLoading(false) } catch (e) { console.error(e) + setIsLoading(false) toaster({ title: `Failed to start server!`, description: 'Please check Server Logs for more details.', @@ -93,6 +97,7 @@ const LocalServerLeftPanel = () => { window.core?.api?.stopServer() setServerEnabled(false) setLoadModelError(undefined) + setIsLoading(false) } const onToggleServer = async () => { @@ -117,6 +122,7 @@ const LocalServerLeftPanel = () => { block theme={serverEnabled ? 'destructive' : 'primary'} disabled={ + isLoading || stateModel.loading || errorRangePort || errorPrefix || @@ -124,7 +130,11 @@ const LocalServerLeftPanel = () => { } onClick={onToggleServer} > - {serverEnabled ? 'Stop' : 'Start'} Server + {isLoading + ? 'Starting...' + : serverEnabled + ? 'Stop Server' + : 'Start Server'} {serverEnabled && (