Faisal Amir d6891a5628
fix: api playground text overlap button and search hub model (#3833)
* fix: button api playground overlap button and search hub model

* fix: update overlap text responsive tab and conditional render GPU menu
2024-10-21 09:57:04 +07:00

264 lines
8.6 KiB
TypeScript

import { Fragment, useCallback, useState } from 'react'
import { Button, Tooltip, Select, Input, Checkbox } from '@janhq/joi'
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { ExternalLinkIcon, InfoIcon } from 'lucide-react'
import { twMerge } from 'tailwind-merge'
import LeftPanelContainer from '@/containers/LeftPanelContainer'
import { toaster } from '@/containers/Toast'
import { useActiveModel, loadModelErrorAtom } from '@/hooks/useActiveModel'
import {
apiServerCorsEnabledAtom,
apiServerHostAtom,
apiServerPortAtom,
apiServerPrefix,
apiServerVerboseLogEnabledAtom,
hostOptions,
} from '@/helpers/atoms/ApiServer.atom'
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
import { selectedModelAtom } from '@/helpers/atoms/Model.atom'
const LocalServerLeftPanel = () => {
const [errorRangePort, setErrorRangePort] = useState(false)
const [errorPrefix, setErrorPrefix] = useState(false)
const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom)
const { startModel, stateModel } = useActiveModel()
const selectedModel = useAtomValue(selectedModelAtom)
const [isCorsEnabled, setIsCorsEnabled] = useAtom(apiServerCorsEnabledAtom)
const [isVerboseEnabled, setIsVerboseEnabled] = useAtom(
apiServerVerboseLogEnabledAtom
)
const [host, setHost] = useAtom(apiServerHostAtom)
const [port, setPort] = useAtom(apiServerPortAtom)
const [prefix, setPrefix] = useAtom(apiServerPrefix)
const setLoadModelError = useSetAtom(loadModelErrorAtom)
const FIRST_TIME_VISIT_API_SERVER = 'firstTimeVisitAPIServer'
const [firstTimeVisitAPIServer, setFirstTimeVisitAPIServer] =
useState<boolean>(false)
const handleChangePort = useCallback(
(value: string) => {
setErrorRangePort(Number(value) <= 0 || Number(value) >= 65536)
setPort(value)
},
[setPort]
)
const handleChangePrefix = useCallback(
(value: string) => {
setErrorPrefix(!value.length || !value.startsWith('/'))
setPrefix(value)
},
[setPrefix]
)
const onStartServerClick = async () => {
if (selectedModel == null) return
try {
const isStarted = await window.core?.api?.startServer({
host,
port,
prefix,
isCorsEnabled,
isVerboseEnabled,
})
if (isStarted) setServerEnabled(true)
if (firstTimeVisitAPIServer) {
localStorage.setItem(FIRST_TIME_VISIT_API_SERVER, 'false')
setFirstTimeVisitAPIServer(false)
}
startModel(selectedModel.id, false).catch((e) => console.error(e))
} catch (e) {
console.error(e)
toaster({
title: `Failed to start server!`,
description: 'Please check Server Logs for more details.',
type: 'error',
})
}
}
const onStopServerClick = async () => {
window.core?.api?.stopServer()
setServerEnabled(false)
setLoadModelError(undefined)
}
const onToggleServer = async () => {
if (serverEnabled) {
await onStopServerClick()
} else {
await onStartServerClick()
}
}
return (
<LeftPanelContainer>
<Fragment>
<div className="p-3">
<p className="leading-relaxed text-[hsla(var(--text-secondary))]">
Start an OpenAI-compatible local HTTP server.
</p>
</div>
<div className="w-full border-b border-[hsla(var(--app-border))] p-3 pb-6">
<div className="-mt-2 flex w-full flex-shrink flex-col gap-y-2">
<Button
block
theme={serverEnabled ? 'destructive' : 'primary'}
disabled={
stateModel.loading ||
errorRangePort ||
errorPrefix ||
!selectedModel
}
onClick={onToggleServer}
>
{serverEnabled ? 'Stop' : 'Start'} Server
</Button>
{serverEnabled && (
<Button variant="soft" asChild className="whitespace-nowrap">
<a href={`http://localhost:${port}`} target="_blank">
<span>API Playground</span>{' '}
<ExternalLinkIcon size={20} className="ml-2" />
</a>
</Button>
)}
</div>
</div>
<Tooltip
trigger={
<div className="p-3">
<p className="mb-2 block font-semibold">Server Options</p>
<div className="flex w-full">
<Select
value={host}
onValueChange={(e) => setHost(e)}
disabled={serverEnabled}
options={hostOptions}
block
/>
</div>
<div className="relative z-50 mt-2 block">
<Input
className={twMerge(
errorRangePort && 'border-[hsla(var(--destructive-bg))]'
)}
type="number"
value={port}
onChange={(e) => {
handleChangePort(e.target.value)
}}
maxLength={5}
disabled={serverEnabled}
/>
</div>
{errorRangePort && (
<p className="mt-2 text-xs text-[hsla(var(--destructive-bg))]">{`The port range should be from 0 to 65536`}</p>
)}
</div>
}
disabled={!serverEnabled}
content="Settings cannot be modified while the server is running"
/>
<div className="space-y-4 px-3">
<div className="block">
<label
id="prefix"
className="mb-2 inline-flex items-start gap-x-2 font-bold "
>
API Prefix
</label>
<div className="block">
<Input
className={twMerge(
'w-full flex-shrink-0',
errorPrefix && 'border-[hsla(var(--destructive-bg))]'
)}
type="text"
value={prefix}
onChange={(e) => {
handleChangePrefix(e.target.value)
}}
disabled={serverEnabled}
/>
</div>
{errorPrefix && (
<p className="mt-2 text-xs text-[hsla(var(--destructive-bg))]">{`Prefix should start with /`}</p>
)}
</div>
<div>
<div className="flex items-center justify-between">
<Checkbox
id="cors"
label={
<div className="flex items-start gap-x-2 ">
<span>Cross-Origin-Resource-Sharing (CORS)</span>
<Tooltip
side="right"
trigger={
<InfoIcon
size={16}
className="mt-0.5 flex-shrink-0 text-[hsla(var(--text-secondary))]"
/>
}
content="CORS (Cross-Origin Resource Sharing) manages resource access on this server from external domains. Enable for secure inter-website communication, regulating data sharing to bolster overall security."
/>
</div>
}
checked={isCorsEnabled}
onChange={(e) => setIsCorsEnabled(e.target.checked)}
name="cors"
disabled={serverEnabled}
/>
</div>
</div>
<div>
<div className="flex items-center justify-between">
<Checkbox
id="verbose"
label={
<div className="flex items-start gap-x-2 ">
<span>Verbose Server Logs</span>
<Tooltip
side="right"
trigger={
<InfoIcon
size={16}
className="mt-0.5 flex-shrink-0 text-[hsla(var(--text-secondary))]"
/>
}
content="Verbose Server Logs provide extensive details about server activities. Enable to capture thorough records, aiding in troubleshooting and monitoring server performance effectively."
/>
</div>
}
checked={isVerboseEnabled}
onChange={(e) => setIsVerboseEnabled(e.target.checked)}
name="verbose"
disabled={serverEnabled}
/>
</div>
</div>
</div>
</Fragment>
</LeftPanelContainer>
)
}
export default LocalServerLeftPanel