Add model response timeout for local api server as configurable value via UI
This commit is contained in:
parent
59b2755810
commit
71e2e24112
@ -162,7 +162,7 @@ pub async fn load_llama_model<R: Runtime>(
|
||||
}
|
||||
|
||||
// Wait for server to be ready or timeout
|
||||
let timeout_duration = Duration::from_secs(180); // 3 minutes timeout
|
||||
let timeout_duration = Duration::from_secs(300); // 5 minutes timeout
|
||||
let start_time = Instant::now();
|
||||
log::info!("Waiting for model session to be ready...");
|
||||
loop {
|
||||
|
||||
@ -13,6 +13,7 @@ pub async fn start_server<R: Runtime>(
|
||||
prefix: String,
|
||||
api_key: String,
|
||||
trusted_hosts: Vec<String>,
|
||||
proxy_timeout: u64,
|
||||
) -> Result<bool, String> {
|
||||
let server_handle = state.server_handle.clone();
|
||||
let plugin_state: State<LlamacppState> = app_handle.state();
|
||||
@ -26,6 +27,7 @@ pub async fn start_server<R: Runtime>(
|
||||
prefix,
|
||||
api_key,
|
||||
vec![trusted_hosts],
|
||||
proxy_timeout,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
@ -631,6 +631,7 @@ pub async fn start_server(
|
||||
prefix: String,
|
||||
proxy_api_key: String,
|
||||
trusted_hosts: Vec<Vec<String>>,
|
||||
proxy_timeout: u64,
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut handle_guard = server_handle.lock().await;
|
||||
if handle_guard.is_some() {
|
||||
@ -648,7 +649,7 @@ pub async fn start_server(
|
||||
};
|
||||
|
||||
let client = Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(300))
|
||||
.timeout(std::time::Duration::from_secs(proxy_timeout))
|
||||
.pool_max_idle_per_host(10)
|
||||
.pool_idle_timeout(std::time::Duration::from_secs(30))
|
||||
.build()?;
|
||||
|
||||
39
web-app/src/containers/ProxyTimeoutInput.tsx
Normal file
39
web-app/src/containers/ProxyTimeoutInput.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useLocalApiServer } from '@/hooks/useLocalApiServer'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useState } from 'react'
|
||||
|
||||
export function ProxyTimeoutInput({ isServerRunning }: { isServerRunning?: boolean }) {
|
||||
const { proxyTimeout, setProxyTimeout } = useLocalApiServer()
|
||||
const [inputValue, setInputValue] = useState(proxyTimeout.toString())
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value
|
||||
setInputValue(value)
|
||||
}
|
||||
|
||||
const handleBlur = () => {
|
||||
const timeout = parseInt(inputValue)
|
||||
if (!isNaN(timeout) && timeout >= 0 && timeout <= 86400) {
|
||||
setProxyTimeout(timeout)
|
||||
} else {
|
||||
// Reset to current value if invalid
|
||||
setInputValue(proxyTimeout.toString())
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
max={86400}
|
||||
value={inputValue}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
className={cn(
|
||||
'w-24 h-8 text-sm',
|
||||
isServerRunning && 'opacity-50 pointer-events-none'
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -32,6 +32,7 @@ describe('useLocalApiServer', () => {
|
||||
store.setVerboseLogs(true)
|
||||
store.setTrustedHosts([])
|
||||
store.setApiKey('')
|
||||
store.setProxyTimeout(600)
|
||||
})
|
||||
|
||||
it('should initialize with default values', () => {
|
||||
@ -45,6 +46,7 @@ describe('useLocalApiServer', () => {
|
||||
expect(result.current.verboseLogs).toBe(true)
|
||||
expect(result.current.trustedHosts).toEqual([])
|
||||
expect(result.current.apiKey).toBe('')
|
||||
expect(result.current.proxyTimeout).toBe(600)
|
||||
})
|
||||
|
||||
describe('enableOnStartup', () => {
|
||||
@ -317,6 +319,32 @@ describe('useLocalApiServer', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('proxyTimeout', () => {
|
||||
it('should set proxy timeout', () => {
|
||||
const { result } = renderHook(() => useLocalApiServer())
|
||||
|
||||
act(() => {
|
||||
result.current.setProxyTimeout(1800)
|
||||
})
|
||||
|
||||
expect(result.current.proxyTimeout).toBe(1800)
|
||||
})
|
||||
|
||||
it('should handle different proxy timeouts', () => {
|
||||
const { result } = renderHook(() => useLocalApiServer())
|
||||
|
||||
const testTimeouts = [100, 300, 600, 3600]
|
||||
|
||||
testTimeouts.forEach((timeout) => {
|
||||
act(() => {
|
||||
result.current.setProxyTimeout(timeout)
|
||||
})
|
||||
|
||||
expect(result.current.proxyTimeout).toBe(timeout)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('state persistence', () => {
|
||||
it('should maintain state across multiple hook instances', () => {
|
||||
const { result: result1 } = renderHook(() => useLocalApiServer())
|
||||
@ -331,6 +359,7 @@ describe('useLocalApiServer', () => {
|
||||
result1.current.setVerboseLogs(false)
|
||||
result1.current.setApiKey('test-key')
|
||||
result1.current.addTrustedHost('example.com')
|
||||
result1.current.setProxyTimeout(1800)
|
||||
})
|
||||
|
||||
expect(result2.current.enableOnStartup).toBe(false)
|
||||
@ -341,6 +370,7 @@ describe('useLocalApiServer', () => {
|
||||
expect(result2.current.verboseLogs).toBe(false)
|
||||
expect(result2.current.apiKey).toBe('test-key')
|
||||
expect(result2.current.trustedHosts).toEqual(['example.com'])
|
||||
expect(result2.current.proxyTimeout).toBe(1800)
|
||||
})
|
||||
})
|
||||
|
||||
@ -356,6 +386,7 @@ describe('useLocalApiServer', () => {
|
||||
result.current.addTrustedHost('localhost')
|
||||
result.current.addTrustedHost('127.0.0.1')
|
||||
result.current.setApiKey('sk-test-key')
|
||||
result.current.setProxyTimeout(800)
|
||||
})
|
||||
|
||||
expect(result.current.serverHost).toBe('0.0.0.0')
|
||||
@ -364,6 +395,7 @@ describe('useLocalApiServer', () => {
|
||||
expect(result.current.corsEnabled).toBe(false)
|
||||
expect(result.current.trustedHosts).toEqual(['localhost', '127.0.0.1'])
|
||||
expect(result.current.apiKey).toBe('sk-test-key')
|
||||
expect(result.current.proxyTimeout).toBe(800)
|
||||
})
|
||||
|
||||
it('should preserve independent state changes', () => {
|
||||
@ -376,6 +408,17 @@ describe('useLocalApiServer', () => {
|
||||
expect(result.current.serverPort).toBe(9000)
|
||||
expect(result.current.serverHost).toBe('127.0.0.1') // Should remain default
|
||||
expect(result.current.apiPrefix).toBe('/v1') // Should remain default
|
||||
expect(result.current.proxyTimeout).toBe(600) // Should remain default
|
||||
|
||||
act(() => {
|
||||
result.current.setProxyTimeout(400)
|
||||
})
|
||||
|
||||
expect(result.current.proxyTimeout).toBe(400)
|
||||
expect(result.current.serverPort).toBe(9000) // Should remain default
|
||||
expect(result.current.serverHost).toBe('127.0.0.1') // Should remain default
|
||||
expect(result.current.apiPrefix).toBe('/v1') // Should remain default
|
||||
|
||||
|
||||
act(() => {
|
||||
result.current.addTrustedHost('example.com')
|
||||
|
||||
@ -28,6 +28,9 @@ type LocalApiServerState = {
|
||||
addTrustedHost: (host: string) => void
|
||||
removeTrustedHost: (host: string) => void
|
||||
setTrustedHosts: (hosts: string[]) => void
|
||||
// Server request timeout (default 600 sec)
|
||||
proxyTimeout: number
|
||||
setProxyTimeout: (value: number) => void
|
||||
}
|
||||
|
||||
export const useLocalApiServer = create<LocalApiServerState>()(
|
||||
@ -55,6 +58,8 @@ export const useLocalApiServer = create<LocalApiServerState>()(
|
||||
trustedHosts: state.trustedHosts.filter((h) => h !== host),
|
||||
})),
|
||||
setTrustedHosts: (hosts) => set({ trustedHosts: hosts }),
|
||||
proxyTimeout: 600,
|
||||
setProxyTimeout: (value) => set({ proxyTimeout: value }),
|
||||
apiKey: '',
|
||||
setApiKey: (value) => set({ apiKey: value }),
|
||||
}),
|
||||
|
||||
@ -180,7 +180,9 @@
|
||||
"cors": "Cross-Origin Resource Sharing (CORS)",
|
||||
"corsDesc": "Erlaube Cross-Origin-Anfragen an den API-Server.",
|
||||
"verboseLogs": "Ausführliche Server Logs",
|
||||
"verboseLogsDesc": "Aktiviere detaillierte Server Logs zum Debuggen"
|
||||
"verboseLogsDesc": "Aktiviere detaillierte Server Logs zum Debuggen",
|
||||
"proxyTimeout": "Zeitüberschreitung bei der Anfrage",
|
||||
"proxyTimeoutDesc": "Wartezeit auf eine Antwort vom lokalen Modell in Sekunden."
|
||||
},
|
||||
"privacy": {
|
||||
"analytics": "Analytik",
|
||||
|
||||
@ -183,7 +183,9 @@
|
||||
"cors": "Cross-Origin Resource Sharing (CORS)",
|
||||
"corsDesc": "Allow cross-origin requests to the API server.",
|
||||
"verboseLogs": "Verbose Server Logs",
|
||||
"verboseLogsDesc": "Enable detailed server logs for debugging."
|
||||
"verboseLogsDesc": "Enable detailed server logs for debugging.",
|
||||
"proxyTimeout": "Request timeout",
|
||||
"proxyTimeoutDesc": "Time to wait for a response from the local model, seconds."
|
||||
},
|
||||
"privacy": {
|
||||
"analytics": "Analytics",
|
||||
|
||||
@ -180,7 +180,9 @@
|
||||
"cors": "Berbagi Sumber Daya Lintas Asal (CORS)",
|
||||
"corsDesc": "Izinkan permintaan lintas asal ke server API.",
|
||||
"verboseLogs": "Log Server Verbose",
|
||||
"verboseLogsDesc": "Aktifkan log server terperinci untuk debugging."
|
||||
"verboseLogsDesc": "Aktifkan log server terperinci untuk debugging.",
|
||||
"proxyTimeout": "Permintaan melebihi batas waktu",
|
||||
"proxyTimeoutDesc": "Waktu tunggu untuk respons dari model lokal dalam detik."
|
||||
},
|
||||
"privacy": {
|
||||
"analytics": "Analitik",
|
||||
|
||||
@ -183,7 +183,9 @@
|
||||
"cors": "Cross-Origin Resource Sharing (CORS)",
|
||||
"corsDesc": "Pozwalaj na żądania cross-origin do serwera API.",
|
||||
"verboseLogs": "Szczegółowe Wpisy Dzienników Serwera",
|
||||
"verboseLogsDesc": "Włącz szczegółowe wpisy dzienników serwera na potrzeby rozwiązywania problemów."
|
||||
"verboseLogsDesc": "Włącz szczegółowe wpisy dzienników serwera na potrzeby rozwiązywania problemów.",
|
||||
"proxyTimeout": "Przekroczenie limitu czasu żądania",
|
||||
"proxyTimeoutDesc": "Czas oczekiwania na odpowiedź od lokalnego modelu w sekundach."
|
||||
},
|
||||
"privacy": {
|
||||
"analytics": "Dane Analityczne",
|
||||
|
||||
@ -180,7 +180,9 @@
|
||||
"cors": "Chia sẻ tài nguyên giữa các nguồn gốc (CORS)",
|
||||
"corsDesc": "Cho phép các yêu cầu cross-origin đến máy chủ API.",
|
||||
"verboseLogs": "Nhật ký máy chủ chi tiết",
|
||||
"verboseLogsDesc": "Bật nhật ký máy chủ chi tiết để gỡ lỗi."
|
||||
"verboseLogsDesc": "Bật nhật ký máy chủ chi tiết để gỡ lỗi.",
|
||||
"proxyTimeout": "Hết thời gian chờ yêu cầu",
|
||||
"proxyTimeoutDesc": "Thời gian chờ phản hồi từ mô hình cục bộ (tính bằng giây)."
|
||||
},
|
||||
"privacy": {
|
||||
"analytics": "Phân tích",
|
||||
|
||||
@ -180,7 +180,9 @@
|
||||
"cors": "跨源资源共享 (CORS)",
|
||||
"corsDesc": "允许跨源请求访问 API 服务器。",
|
||||
"verboseLogs": "详细服务器日志",
|
||||
"verboseLogsDesc": "启用详细服务器日志以进行调试。"
|
||||
"verboseLogsDesc": "启用详细服务器日志以进行调试。",
|
||||
"proxyTimeout": "请求超时",
|
||||
"proxyTimeoutDesc": "等待本地模型响应的时间(单位:秒)。"
|
||||
},
|
||||
"privacy": {
|
||||
"analytics": "分析",
|
||||
|
||||
@ -180,7 +180,9 @@
|
||||
"cors": "跨來源資源共用 (CORS)",
|
||||
"corsDesc": "允許跨來源請求存取 API 伺服器。",
|
||||
"verboseLogs": "詳細伺服器日誌",
|
||||
"verboseLogsDesc": "啟用詳細伺服器日誌以進行偵錯。"
|
||||
"verboseLogsDesc": "啟用詳細伺服器日誌以進行偵錯。",
|
||||
"proxyTimeout": "請求逾時",
|
||||
"proxyTimeoutDesc": "等待本地模型回應的時間(秒)。"
|
||||
},
|
||||
"privacy": {
|
||||
"analytics": "分析",
|
||||
|
||||
@ -36,6 +36,7 @@ export function DataProvider() {
|
||||
trustedHosts,
|
||||
corsEnabled,
|
||||
verboseLogs,
|
||||
proxyTimeout,
|
||||
} = useLocalApiServer()
|
||||
const { setServerStatus } = useAppState()
|
||||
|
||||
@ -169,6 +170,7 @@ export function DataProvider() {
|
||||
trustedHosts,
|
||||
isCorsEnabled: corsEnabled,
|
||||
isVerboseEnabled: verboseLogs,
|
||||
proxyTimeout: proxyTimeout,
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { useTranslation } from '@/i18n/react-i18next-compat'
|
||||
import { ServerHostSwitcher } from '@/containers/ServerHostSwitcher'
|
||||
import { PortInput } from '@/containers/PortInput'
|
||||
import { ProxyTimeoutInput } from '@/containers/ProxyTimeoutInput'
|
||||
import { ApiPrefixInput } from '@/containers/ApiPrefixInput'
|
||||
import { TrustedHostsInput } from '@/containers/TrustedHostsInput'
|
||||
import { useLocalApiServer } from '@/hooks/useLocalApiServer'
|
||||
@ -50,6 +51,7 @@ function LocalAPIServerContent() {
|
||||
apiPrefix,
|
||||
apiKey,
|
||||
trustedHosts,
|
||||
proxyTimeout,
|
||||
} = useLocalApiServer()
|
||||
|
||||
const { serverStatus, setServerStatus } = useAppState()
|
||||
@ -157,6 +159,7 @@ function LocalAPIServerContent() {
|
||||
trustedHosts,
|
||||
isCorsEnabled: corsEnabled,
|
||||
isVerboseEnabled: verboseLogs,
|
||||
proxyTimeout: proxyTimeout,
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
@ -311,6 +314,11 @@ function LocalAPIServerContent() {
|
||||
<TrustedHostsInput isServerRunning={isServerRunning} />
|
||||
}
|
||||
/>
|
||||
<CardItem
|
||||
title={t('settings:localApiServer.proxyTimeout')}
|
||||
description={t('settings:localApiServer.proxyTimeoutDesc')}
|
||||
actions={<ProxyTimeoutInput isServerRunning={isServerRunning} />}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* Advanced Settings */}
|
||||
|
||||
@ -72,6 +72,10 @@ A mandatory secret key to authenticate requests.
|
||||
### Trusted Hosts
|
||||
A comma-separated list of hostnames allowed to access the server. This provides an additional layer of security when the server is exposed on your network.
|
||||
|
||||
### Request timeout
|
||||
Request timeout for local model response in seconds.
|
||||
- **`600`** (Default): You can change this to any suitable value.
|
||||
|
||||
## Advanced Settings
|
||||
|
||||
### Cross-Origin Resource Sharing (CORS)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user