Merge pull request #6364 from menloresearch/feat/local-api-server

feat: allow see Apikey when server local status running
This commit is contained in:
Faisal Amir 2025-09-03 20:05:57 +07:00 committed by GitHub
commit b2c4e89402
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 70 additions and 40 deletions

View File

@ -3,15 +3,18 @@ import { useLocalApiServer } from '@/hooks/useLocalApiServer'
import { useState, useEffect, useCallback } from 'react' import { useState, useEffect, useCallback } from 'react'
import { Eye, EyeOff } from 'lucide-react' import { Eye, EyeOff } from 'lucide-react'
import { useTranslation } from '@/i18n/react-i18next-compat' import { useTranslation } from '@/i18n/react-i18next-compat'
import { cn } from '@/lib/utils'
interface ApiKeyInputProps { interface ApiKeyInputProps {
showError?: boolean showError?: boolean
onValidationChange?: (isValid: boolean) => void onValidationChange?: (isValid: boolean) => void
isServerRunning?: boolean
} }
export function ApiKeyInput({ export function ApiKeyInput({
showError = false, showError = false,
onValidationChange, onValidationChange,
isServerRunning,
}: ApiKeyInputProps) { }: ApiKeyInputProps) {
const { apiKey, setApiKey } = useLocalApiServer() const { apiKey, setApiKey } = useLocalApiServer()
const [inputValue, setInputValue] = useState(apiKey.toString()) const [inputValue, setInputValue] = useState(apiKey.toString())
@ -19,7 +22,8 @@ export function ApiKeyInput({
const [error, setError] = useState('') const [error, setError] = useState('')
const { t } = useTranslation() const { t } = useTranslation()
const validateApiKey = useCallback((value: string) => { const validateApiKey = useCallback(
(value: string) => {
if (!value || value.trim().length === 0) { if (!value || value.trim().length === 0) {
setError(t('common:apiKeyRequired')) setError(t('common:apiKeyRequired'))
onValidationChange?.(false) onValidationChange?.(false)
@ -28,7 +32,9 @@ export function ApiKeyInput({
setError('') setError('')
onValidationChange?.(true) onValidationChange?.(true)
return true return true
}, [onValidationChange, t]) },
[onValidationChange, t]
)
useEffect(() => { useEffect(() => {
if (showError) { if (showError) {
@ -64,11 +70,12 @@ export function ApiKeyInput({
value={inputValue} value={inputValue}
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur} onBlur={handleBlur}
className={`w-full text-sm pr-10 ${ className={cn(
hasError 'w-full text-sm pr-10',
? 'border-1 border-destructive focus:border-destructive focus:ring-destructive' hasError &&
: '' 'border-1 border-destructive focus:border-destructive focus:ring-destructive',
}`} isServerRunning && 'opacity-50 pointer-events-none'
)}
placeholder={t('common:enterApiKey')} placeholder={t('common:enterApiKey')}
/> />
<div className="absolute right-2 top-1/2 transform -translate-y-1/2 flex items-center gap-1"> <div className="absolute right-2 top-1/2 transform -translate-y-1/2 flex items-center gap-1">

View File

@ -1,8 +1,13 @@
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { useLocalApiServer } from '@/hooks/useLocalApiServer' import { useLocalApiServer } from '@/hooks/useLocalApiServer'
import { cn } from '@/lib/utils'
import { useState } from 'react' import { useState } from 'react'
export function ApiPrefixInput() { export function ApiPrefixInput({
isServerRunning,
}: {
isServerRunning?: boolean
}) {
const { apiPrefix, setApiPrefix } = useLocalApiServer() const { apiPrefix, setApiPrefix } = useLocalApiServer()
const [inputValue, setInputValue] = useState(apiPrefix) const [inputValue, setInputValue] = useState(apiPrefix)
@ -27,7 +32,10 @@ export function ApiPrefixInput() {
value={inputValue} value={inputValue}
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur} onBlur={handleBlur}
className="w-24 h-8 text-sm" className={cn(
'w-24 h-8 text-sm',
isServerRunning && 'opacity-50 pointer-events-none'
)}
placeholder="/v1" placeholder="/v1"
/> />
) )

View File

@ -1,8 +1,9 @@
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { useLocalApiServer } from '@/hooks/useLocalApiServer' import { useLocalApiServer } from '@/hooks/useLocalApiServer'
import { cn } from '@/lib/utils'
import { useState } from 'react' import { useState } from 'react'
export function PortInput() { export function PortInput({ isServerRunning }: { isServerRunning?: boolean }) {
const { serverPort, setServerPort } = useLocalApiServer() const { serverPort, setServerPort } = useLocalApiServer()
const [inputValue, setInputValue] = useState(serverPort.toString()) const [inputValue, setInputValue] = useState(serverPort.toString())
@ -29,7 +30,10 @@ export function PortInput() {
value={inputValue} value={inputValue}
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur} onBlur={handleBlur}
className="w-24 h-8 text-sm" className={cn(
'w-24 h-8 text-sm',
isServerRunning && 'opacity-50 pointer-events-none'
)}
/> />
) )
} }

View File

@ -4,6 +4,7 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu' } from '@/components/ui/dropdown-menu'
import { useLocalApiServer } from '@/hooks/useLocalApiServer' import { useLocalApiServer } from '@/hooks/useLocalApiServer'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
@ -12,12 +13,19 @@ const hostOptions = [
{ value: '0.0.0.0', label: '0.0.0.0' }, { value: '0.0.0.0', label: '0.0.0.0' },
] ]
export function ServerHostSwitcher() { export function ServerHostSwitcher({
isServerRunning,
}: {
isServerRunning?: boolean
}) {
const { serverHost, setServerHost } = useLocalApiServer() const { serverHost, setServerHost } = useLocalApiServer()
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger
asChild
className={cn(isServerRunning && 'opacity-50 pointer-events-none')}
>
<span <span
title="Edit Server Host" title="Edit Server Host"
className="flex cursor-pointer items-center gap-1 px-2 py-1 rounded-sm bg-main-view-fg/15 text-sm outline-none text-main-view-fg font-medium" className="flex cursor-pointer items-center gap-1 px-2 py-1 rounded-sm bg-main-view-fg/15 text-sm outline-none text-main-view-fg font-medium"

View File

@ -2,8 +2,13 @@ import { Input } from '@/components/ui/input'
import { useLocalApiServer } from '@/hooks/useLocalApiServer' import { useLocalApiServer } from '@/hooks/useLocalApiServer'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useTranslation } from '@/i18n/react-i18next-compat' import { useTranslation } from '@/i18n/react-i18next-compat'
import { cn } from '@/lib/utils'
export function TrustedHostsInput() { export function TrustedHostsInput({
isServerRunning,
}: {
isServerRunning?: boolean
}) {
const { trustedHosts, setTrustedHosts } = useLocalApiServer() const { trustedHosts, setTrustedHosts } = useLocalApiServer()
const [inputValue, setInputValue] = useState(trustedHosts.join(', ')) const [inputValue, setInputValue] = useState(trustedHosts.join(', '))
const { t } = useTranslation() const { t } = useTranslation()
@ -38,8 +43,11 @@ export function TrustedHostsInput() {
value={inputValue} value={inputValue}
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur} onBlur={handleBlur}
className="w-full h-8 text-sm"
placeholder={t('common:enterTrustedHosts')} placeholder={t('common:enterTrustedHosts')}
className={cn(
'w-24 h-8 text-sm',
isServerRunning && 'opacity-50 pointer-events-none'
)}
/> />
) )
} }

View File

@ -45,7 +45,8 @@ function LocalAPIServer() {
} = useLocalApiServer() } = useLocalApiServer()
const { serverStatus, setServerStatus } = useAppState() const { serverStatus, setServerStatus } = useAppState()
const { selectedModel, selectedProvider, getProviderByName } = useModelProvider() const { selectedModel, selectedProvider, getProviderByName } =
useModelProvider()
const [showApiKeyError, setShowApiKeyError] = useState(false) const [showApiKeyError, setShowApiKeyError] = useState(false)
const [isApiKeyEmpty, setIsApiKeyEmpty] = useState( const [isApiKeyEmpty, setIsApiKeyEmpty] = useState(
!apiKey || apiKey.toString().trim().length === 0 !apiKey || apiKey.toString().trim().length === 0
@ -293,38 +294,31 @@ function LocalAPIServer() {
<CardItem <CardItem
title={t('settings:localApiServer.serverHost')} title={t('settings:localApiServer.serverHost')}
description={t('settings:localApiServer.serverHostDesc')} description={t('settings:localApiServer.serverHostDesc')}
className={cn( actions={
isServerRunning && 'opacity-50 pointer-events-none' <ServerHostSwitcher isServerRunning={isServerRunning} />
)} }
actions={<ServerHostSwitcher />}
/> />
<CardItem <CardItem
title={t('settings:localApiServer.serverPort')} title={t('settings:localApiServer.serverPort')}
description={t('settings:localApiServer.serverPortDesc')} description={t('settings:localApiServer.serverPortDesc')}
className={cn( actions={<PortInput isServerRunning={isServerRunning} />}
isServerRunning && 'opacity-50 pointer-events-none'
)}
actions={<PortInput />}
/> />
<CardItem <CardItem
title={t('settings:localApiServer.apiPrefix')} title={t('settings:localApiServer.apiPrefix')}
description={t('settings:localApiServer.apiPrefixDesc')} description={t('settings:localApiServer.apiPrefixDesc')}
className={cn( actions={<ApiPrefixInput isServerRunning={isServerRunning} />}
isServerRunning && 'opacity-50 pointer-events-none'
)}
actions={<ApiPrefixInput />}
/> />
<CardItem <CardItem
title={t('settings:localApiServer.apiKey')} title={t('settings:localApiServer.apiKey')}
description={t('settings:localApiServer.apiKeyDesc')} description={t('settings:localApiServer.apiKeyDesc')}
className={cn( className={cn(
'flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-y-2', 'flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-y-2',
isServerRunning && 'opacity-50 pointer-events-none',
isApiKeyEmpty && showApiKeyError && 'pb-6' isApiKeyEmpty && showApiKeyError && 'pb-6'
)} )}
classNameWrapperAction="w-full sm:w-auto" classNameWrapperAction="w-full sm:w-auto"
actions={ actions={
<ApiKeyInput <ApiKeyInput
isServerRunning={isServerRunning}
showError={showApiKeyError} showError={showApiKeyError}
onValidationChange={handleApiKeyValidation} onValidationChange={handleApiKeyValidation}
/> />
@ -334,11 +328,12 @@ function LocalAPIServer() {
title={t('settings:localApiServer.trustedHosts')} title={t('settings:localApiServer.trustedHosts')}
description={t('settings:localApiServer.trustedHostsDesc')} description={t('settings:localApiServer.trustedHostsDesc')}
className={cn( className={cn(
'flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-y-2', 'flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-y-2'
isServerRunning && 'opacity-50 pointer-events-none'
)} )}
classNameWrapperAction="w-full sm:w-auto" classNameWrapperAction="w-full sm:w-auto"
actions={<TrustedHostsInput />} actions={
<TrustedHostsInput isServerRunning={isServerRunning} />
}
/> />
</Card> </Card>