enhance ux local server page (#4045)
This commit is contained in:
parent
05019f7236
commit
43eff865ff
@ -1,8 +1,8 @@
|
|||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* 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 { useAtomValue } from 'jotai'
|
||||||
|
|
||||||
import { FolderIcon, CheckIcon, CopyIcon } from 'lucide-react'
|
import { FolderIcon, CheckIcon, CopyIcon } from 'lucide-react'
|
||||||
@ -22,6 +22,9 @@ const ServerLogs = (props: ServerLogsProps) => {
|
|||||||
const { getLogs } = useLogs()
|
const { getLogs } = useLogs()
|
||||||
const serverEnabled = useAtomValue(serverEnabledAtom)
|
const serverEnabled = useAtomValue(serverEnabledAtom)
|
||||||
const [logs, setLogs] = useState<string[]>([])
|
const [logs, setLogs] = useState<string[]>([])
|
||||||
|
const listRef = useRef<HTMLDivElement>(null)
|
||||||
|
const prevScrollTop = useRef(0)
|
||||||
|
const isUserManuallyScrollingUp = useRef(false)
|
||||||
|
|
||||||
const updateLogs = useCallback(
|
const updateLogs = useCallback(
|
||||||
() =>
|
() =>
|
||||||
@ -58,13 +61,45 @@ const ServerLogs = (props: ServerLogsProps) => {
|
|||||||
|
|
||||||
const clipboard = useClipboard({ timeout: 1000 })
|
const clipboard = useClipboard({ timeout: 1000 })
|
||||||
|
|
||||||
|
const handleScroll = useCallback((event: React.UIEvent<HTMLElement>) => {
|
||||||
|
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 (
|
return (
|
||||||
<div
|
<ScrollArea
|
||||||
|
ref={listRef}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'p-4 pb-0',
|
'h-[calc(100%-49px)] w-full p-4 py-0',
|
||||||
!withCopy && 'max-w-[38vw] lg:max-w-[40vw] xl:max-w-[50vw]',
|
|
||||||
logs.length === 0 && 'mx-auto'
|
logs.length === 0 && 'mx-auto'
|
||||||
)}
|
)}
|
||||||
|
onScroll={handleScroll}
|
||||||
>
|
>
|
||||||
{withCopy && (
|
{withCopy && (
|
||||||
<div className="absolute right-2 top-7">
|
<div className="absolute right-2 top-7">
|
||||||
@ -107,7 +142,7 @@ const ServerLogs = (props: ServerLogsProps) => {
|
|||||||
)}
|
)}
|
||||||
<div className="flex h-full w-full flex-col">
|
<div className="flex h-full w-full flex-col">
|
||||||
{logs.length > 0 ? (
|
{logs.length > 0 ? (
|
||||||
<code className="inline-block whitespace-break-spaces text-[13px]">
|
<code className="inline-block max-w-[38vw] whitespace-break-spaces text-[13px] lg:max-w-[40vw] xl:max-w-[50vw]">
|
||||||
{logs.slice(-limit).map((log, i) => {
|
{logs.slice(-limit).map((log, i) => {
|
||||||
return (
|
return (
|
||||||
<p key={i} className="my-2 leading-relaxed">
|
<p key={i} className="my-2 leading-relaxed">
|
||||||
@ -256,7 +291,7 @@ const ServerLogs = (props: ServerLogsProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ScrollArea>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { Button, ScrollArea } from '@janhq/joi'
|
import { Button } from '@janhq/joi'
|
||||||
import { CodeIcon, Paintbrush } from 'lucide-react'
|
import { CodeIcon, Paintbrush } from 'lucide-react'
|
||||||
|
|
||||||
import { InfoIcon } from 'lucide-react'
|
import { InfoIcon } from 'lucide-react'
|
||||||
@ -26,8 +26,8 @@ const LocalServerCenterPanel = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CenterPanelContainer>
|
<CenterPanelContainer>
|
||||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
<div className="flex h-full w-full flex-col">
|
||||||
<div className="sticky top-0 flex items-center justify-between border-b border-[hsla(var(--app-border))] px-4 py-2">
|
<div className="sticky top-0 z-10 flex items-center justify-between border-b border-[hsla(var(--app-border))] bg-[hsla(var(--app-bg))] px-4 py-2">
|
||||||
<h2 className="font-bold">Server Logs</h2>
|
<h2 className="font-bold">Server Logs</h2>
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
<Button
|
<Button
|
||||||
@ -72,9 +72,7 @@ const LocalServerCenterPanel = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ScrollArea className="h-full w-full">
|
<ServerLogs />
|
||||||
<ServerLogs />
|
|
||||||
</ScrollArea>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CenterPanelContainer>
|
</CenterPanelContainer>
|
||||||
|
|||||||
@ -29,6 +29,7 @@ const LocalServerLeftPanel = () => {
|
|||||||
const [errorRangePort, setErrorRangePort] = useState(false)
|
const [errorRangePort, setErrorRangePort] = useState(false)
|
||||||
const [errorPrefix, setErrorPrefix] = useState(false)
|
const [errorPrefix, setErrorPrefix] = useState(false)
|
||||||
const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom)
|
const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
const { startModel, stateModel } = useActiveModel()
|
const { startModel, stateModel } = useActiveModel()
|
||||||
const selectedModel = useAtomValue(selectedModelAtom)
|
const selectedModel = useAtomValue(selectedModelAtom)
|
||||||
@ -66,6 +67,7 @@ const LocalServerLeftPanel = () => {
|
|||||||
const onStartServerClick = async () => {
|
const onStartServerClick = async () => {
|
||||||
if (selectedModel == null) return
|
if (selectedModel == null) return
|
||||||
try {
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
const isStarted = await window.core?.api?.startServer({
|
const isStarted = await window.core?.api?.startServer({
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
@ -79,8 +81,10 @@ const LocalServerLeftPanel = () => {
|
|||||||
setFirstTimeVisitAPIServer(false)
|
setFirstTimeVisitAPIServer(false)
|
||||||
}
|
}
|
||||||
startModel(selectedModel.id, false).catch((e) => console.error(e))
|
startModel(selectedModel.id, false).catch((e) => console.error(e))
|
||||||
|
setIsLoading(false)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
setIsLoading(false)
|
||||||
toaster({
|
toaster({
|
||||||
title: `Failed to start server!`,
|
title: `Failed to start server!`,
|
||||||
description: 'Please check Server Logs for more details.',
|
description: 'Please check Server Logs for more details.',
|
||||||
@ -93,6 +97,7 @@ const LocalServerLeftPanel = () => {
|
|||||||
window.core?.api?.stopServer()
|
window.core?.api?.stopServer()
|
||||||
setServerEnabled(false)
|
setServerEnabled(false)
|
||||||
setLoadModelError(undefined)
|
setLoadModelError(undefined)
|
||||||
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onToggleServer = async () => {
|
const onToggleServer = async () => {
|
||||||
@ -117,6 +122,7 @@ const LocalServerLeftPanel = () => {
|
|||||||
block
|
block
|
||||||
theme={serverEnabled ? 'destructive' : 'primary'}
|
theme={serverEnabled ? 'destructive' : 'primary'}
|
||||||
disabled={
|
disabled={
|
||||||
|
isLoading ||
|
||||||
stateModel.loading ||
|
stateModel.loading ||
|
||||||
errorRangePort ||
|
errorRangePort ||
|
||||||
errorPrefix ||
|
errorPrefix ||
|
||||||
@ -124,7 +130,11 @@ const LocalServerLeftPanel = () => {
|
|||||||
}
|
}
|
||||||
onClick={onToggleServer}
|
onClick={onToggleServer}
|
||||||
>
|
>
|
||||||
{serverEnabled ? 'Stop' : 'Start'} Server
|
{isLoading
|
||||||
|
? 'Starting...'
|
||||||
|
: serverEnabled
|
||||||
|
? 'Stop Server'
|
||||||
|
: 'Start Server'}
|
||||||
</Button>
|
</Button>
|
||||||
{serverEnabled && (
|
{serverEnabled && (
|
||||||
<Button variant="soft" asChild className="whitespace-nowrap">
|
<Button variant="soft" asChild className="whitespace-nowrap">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user