mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 05:39:05 +08:00
fix: api server status (#10734)
* refactor(apiServer): move api server types to dedicated module Restructure api server type definitions by moving them from index.ts to a dedicated apiServer.ts file. This improves code organization and maintainability by grouping related types together. * feat(api-server): add api server management hooks and integration Extract api server management logic into reusable hook and integrate with settings page * feat(api-server): improve api server status handling and error messages - add new error messages for api server status - optimize initial state and loading in useApiServer hook - centralize api server enabled check via useApiServer hook - update components to use new api server status handling * fix(agents): update error message key for agent server not running * fix(i18n): update api server status messages across locales Remove redundant 'notRunning' message in en-us locale Add consistent 'not_running' error message in all locales Add missing 'notEnabled' message in several locales * refactor: update api server type imports to use @types Move api server related type imports from renderer/src/types to @types package for better code organization and maintainability * docs(IpcChannel): add comment about unused api-server:get-config Add TODO comment about data inconsistency in useApiServer hook * refactor(assistants): pass apiServerEnabled as prop instead of using hook Move apiServerEnabled from being fetched via useApiServer hook to being passed as a prop through component hierarchy. This improves maintainability by making dependencies more explicit and reducing hook usage in child components. * style(AssistantsTab): add consistent margin-bottom to alert components * feat(useAgent): add api server status checks before fetching agent Ensure api server is enabled and running before attempting to fetch agent data
This commit is contained in:
parent
2e173631a0
commit
1a972ac0e0
@ -317,6 +317,7 @@ export enum IpcChannel {
|
|||||||
ApiServer_Stop = 'api-server:stop',
|
ApiServer_Stop = 'api-server:stop',
|
||||||
ApiServer_Restart = 'api-server:restart',
|
ApiServer_Restart = 'api-server:restart',
|
||||||
ApiServer_GetStatus = 'api-server:get-status',
|
ApiServer_GetStatus = 'api-server:get-status',
|
||||||
|
// NOTE: This api is not be used.
|
||||||
ApiServer_GetConfig = 'api-server:get-config',
|
ApiServer_GetConfig = 'api-server:get-config',
|
||||||
|
|
||||||
// Anthropic OAuth
|
// Anthropic OAuth
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
import { IpcChannel } from '@shared/IpcChannel'
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
import { ApiServerConfig } from '@types'
|
import {
|
||||||
|
ApiServerConfig,
|
||||||
|
GetApiServerStatusResult,
|
||||||
|
RestartApiServerStatusResult,
|
||||||
|
StartApiServerStatusResult,
|
||||||
|
StopApiServerStatusResult
|
||||||
|
} from '@types'
|
||||||
import { ipcMain } from 'electron'
|
import { ipcMain } from 'electron'
|
||||||
|
|
||||||
import { apiServer } from '../apiServer'
|
import { apiServer } from '../apiServer'
|
||||||
@ -52,7 +58,7 @@ export class ApiServerService {
|
|||||||
|
|
||||||
registerIpcHandlers(): void {
|
registerIpcHandlers(): void {
|
||||||
// API Server
|
// API Server
|
||||||
ipcMain.handle(IpcChannel.ApiServer_Start, async () => {
|
ipcMain.handle(IpcChannel.ApiServer_Start, async (): Promise<StartApiServerStatusResult> => {
|
||||||
try {
|
try {
|
||||||
await this.start()
|
await this.start()
|
||||||
return { success: true }
|
return { success: true }
|
||||||
@ -61,7 +67,7 @@ export class ApiServerService {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle(IpcChannel.ApiServer_Stop, async () => {
|
ipcMain.handle(IpcChannel.ApiServer_Stop, async (): Promise<StopApiServerStatusResult> => {
|
||||||
try {
|
try {
|
||||||
await this.stop()
|
await this.stop()
|
||||||
return { success: true }
|
return { success: true }
|
||||||
@ -70,7 +76,7 @@ export class ApiServerService {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle(IpcChannel.ApiServer_Restart, async () => {
|
ipcMain.handle(IpcChannel.ApiServer_Restart, async (): Promise<RestartApiServerStatusResult> => {
|
||||||
try {
|
try {
|
||||||
await this.restart()
|
await this.restart()
|
||||||
return { success: true }
|
return { success: true }
|
||||||
@ -79,7 +85,7 @@ export class ApiServerService {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle(IpcChannel.ApiServer_GetStatus, async () => {
|
ipcMain.handle(IpcChannel.ApiServer_GetStatus, async (): Promise<GetApiServerStatusResult> => {
|
||||||
try {
|
try {
|
||||||
const config = await this.getCurrentConfig()
|
const config = await this.getCurrentConfig()
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
FileListResponse,
|
FileListResponse,
|
||||||
FileMetadata,
|
FileMetadata,
|
||||||
FileUploadResponse,
|
FileUploadResponse,
|
||||||
|
GetApiServerStatusResult,
|
||||||
KnowledgeBaseParams,
|
KnowledgeBaseParams,
|
||||||
KnowledgeItem,
|
KnowledgeItem,
|
||||||
KnowledgeSearchResult,
|
KnowledgeSearchResult,
|
||||||
@ -22,8 +23,11 @@ import {
|
|||||||
OcrProvider,
|
OcrProvider,
|
||||||
OcrResult,
|
OcrResult,
|
||||||
Provider,
|
Provider,
|
||||||
|
RestartApiServerStatusResult,
|
||||||
S3Config,
|
S3Config,
|
||||||
Shortcut,
|
Shortcut,
|
||||||
|
StartApiServerStatusResult,
|
||||||
|
StopApiServerStatusResult,
|
||||||
SupportedOcrFile,
|
SupportedOcrFile,
|
||||||
ThemeMode,
|
ThemeMode,
|
||||||
WebDavConfig
|
WebDavConfig
|
||||||
@ -496,6 +500,12 @@ const api = {
|
|||||||
ipcRenderer.removeListener(channel, listener)
|
ipcRenderer.removeListener(channel, listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
apiServer: {
|
||||||
|
getStatus: (): Promise<GetApiServerStatusResult> => ipcRenderer.invoke(IpcChannel.ApiServer_GetStatus),
|
||||||
|
start: (): Promise<StartApiServerStatusResult> => ipcRenderer.invoke(IpcChannel.ApiServer_Start),
|
||||||
|
restart: (): Promise<RestartApiServerStatusResult> => ipcRenderer.invoke(IpcChannel.ApiServer_Restart),
|
||||||
|
stop: (): Promise<StopApiServerStatusResult> => ipcRenderer.invoke(IpcChannel.ApiServer_Stop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,28 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
|
|
||||||
|
import { useApiServer } from '../useApiServer'
|
||||||
import { useAgentClient } from './useAgentClient'
|
import { useAgentClient } from './useAgentClient'
|
||||||
|
|
||||||
export const useAgent = (id: string | null) => {
|
export const useAgent = (id: string | null) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const client = useAgentClient()
|
const client = useAgentClient()
|
||||||
const key = id ? client.agentPaths.withId(id) : null
|
const key = id ? client.agentPaths.withId(id) : null
|
||||||
|
const { apiServerConfig, apiServerRunning } = useApiServer()
|
||||||
const fetcher = useCallback(async () => {
|
const fetcher = useCallback(async () => {
|
||||||
if (!id || id === 'fake') {
|
if (!id || id === 'fake') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
if (!apiServerConfig.enabled) {
|
||||||
|
throw new Error(t('apiServer.messages.notEnabled'))
|
||||||
|
}
|
||||||
|
if (!apiServerRunning) {
|
||||||
|
throw new Error(t('agent.server.error.not_running'))
|
||||||
|
}
|
||||||
const result = await client.getAgent(id)
|
const result = await client.getAgent(id)
|
||||||
return result
|
return result
|
||||||
}, [client, id])
|
}, [apiServerConfig.enabled, apiServerRunning, client, id, t])
|
||||||
const { data, error, isLoading } = useSWR(key, id ? fetcher : null)
|
const { data, error, isLoading } = useSWR(key, id ? fetcher : null)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useCallback } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
|
|
||||||
|
import { useApiServer } from '../useApiServer'
|
||||||
import { useRuntime } from '../useRuntime'
|
import { useRuntime } from '../useRuntime'
|
||||||
import { useAgentClient } from './useAgentClient'
|
import { useAgentClient } from './useAgentClient'
|
||||||
|
|
||||||
@ -23,11 +24,18 @@ export const useAgents = () => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const client = useAgentClient()
|
const client = useAgentClient()
|
||||||
const key = client.agentPaths.base
|
const key = client.agentPaths.base
|
||||||
|
const { apiServerConfig, apiServerRunning } = useApiServer()
|
||||||
const fetcher = useCallback(async () => {
|
const fetcher = useCallback(async () => {
|
||||||
|
if (!apiServerConfig.enabled) {
|
||||||
|
throw new Error(t('apiServer.messages.notEnabled'))
|
||||||
|
}
|
||||||
|
if (!apiServerRunning) {
|
||||||
|
throw new Error(t('agent.server.error.not_running'))
|
||||||
|
}
|
||||||
const result = await client.listAgents()
|
const result = await client.listAgents()
|
||||||
// NOTE: We only use the array for now. useUpdateAgent depends on this behavior.
|
// NOTE: We only use the array for now. useUpdateAgent depends on this behavior.
|
||||||
return result.data
|
return result.data
|
||||||
}, [client])
|
}, [apiServerConfig.enabled, apiServerRunning, client, t])
|
||||||
const { data, error, isLoading, mutate } = useSWR(key, fetcher)
|
const { data, error, isLoading, mutate } = useSWR(key, fetcher)
|
||||||
const { chat } = useRuntime()
|
const { chat } = useRuntime()
|
||||||
const { activeAgentId } = chat
|
const { activeAgentId } = chat
|
||||||
|
|||||||
112
src/renderer/src/hooks/useApiServer.ts
Normal file
112
src/renderer/src/hooks/useApiServer.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
|
import { setApiServerEnabled as setApiServerEnabledAction } from '@renderer/store/settings'
|
||||||
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('useApiServer')
|
||||||
|
|
||||||
|
export const useApiServer = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
// FIXME: We currently store two copies of the config data in both the renderer and the main processes,
|
||||||
|
// which carries the risk of data inconsistency. This should be modified so that the main process stores
|
||||||
|
// the data, and the renderer retrieves it.
|
||||||
|
const apiServerConfig = useAppSelector((state) => state.settings.apiServer)
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
// Optimistic initial state.
|
||||||
|
const [apiServerRunning, setApiServerRunning] = useState(apiServerConfig.enabled)
|
||||||
|
const [apiServerLoading, setApiServerLoading] = useState(true)
|
||||||
|
|
||||||
|
const setApiServerEnabled = useCallback(
|
||||||
|
(enabled: boolean) => {
|
||||||
|
dispatch(setApiServerEnabledAction(enabled))
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
)
|
||||||
|
|
||||||
|
// API Server functions
|
||||||
|
const checkApiServerStatus = useCallback(async () => {
|
||||||
|
setApiServerLoading(true)
|
||||||
|
try {
|
||||||
|
const status = await window.api.apiServer.getStatus()
|
||||||
|
setApiServerRunning(status.running)
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error('Failed to check API server status:', error)
|
||||||
|
} finally {
|
||||||
|
setApiServerLoading(false)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const startApiServer = useCallback(async () => {
|
||||||
|
if (apiServerLoading) return
|
||||||
|
|
||||||
|
setApiServerLoading(true)
|
||||||
|
try {
|
||||||
|
const result = await window.api.apiServer.start()
|
||||||
|
if (result.success) {
|
||||||
|
setApiServerRunning(true)
|
||||||
|
window.toast.success(t('apiServer.messages.startSuccess'))
|
||||||
|
} else {
|
||||||
|
window.toast.error(t('apiServer.messages.startError') + result.error)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
window.toast.error(t('apiServer.messages.startError') + (error.message || error))
|
||||||
|
} finally {
|
||||||
|
setApiServerLoading(false)
|
||||||
|
}
|
||||||
|
}, [apiServerLoading, t])
|
||||||
|
|
||||||
|
const stopApiServer = useCallback(async () => {
|
||||||
|
if (apiServerLoading) return
|
||||||
|
|
||||||
|
setApiServerLoading(true)
|
||||||
|
try {
|
||||||
|
const result = await window.api.apiServer.stop()
|
||||||
|
if (result.success) {
|
||||||
|
setApiServerRunning(false)
|
||||||
|
window.toast.success(t('apiServer.messages.stopSuccess'))
|
||||||
|
} else {
|
||||||
|
window.toast.error(t('apiServer.messages.stopError') + result.error)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
window.toast.error(t('apiServer.messages.stopError') + (error.message || error))
|
||||||
|
} finally {
|
||||||
|
setApiServerLoading(false)
|
||||||
|
}
|
||||||
|
}, [apiServerLoading, t])
|
||||||
|
|
||||||
|
const restartApiServer = useCallback(async () => {
|
||||||
|
if (apiServerLoading) return
|
||||||
|
|
||||||
|
setApiServerLoading(true)
|
||||||
|
try {
|
||||||
|
const result = await window.api.apiServer.restart()
|
||||||
|
if (result.success) {
|
||||||
|
await checkApiServerStatus()
|
||||||
|
window.toast.success(t('apiServer.messages.restartSuccess'))
|
||||||
|
} else {
|
||||||
|
window.toast.error(t('apiServer.messages.restartError') + result.error)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
window.toast.error(t('apiServer.messages.restartFailed') + (error as Error).message)
|
||||||
|
} finally {
|
||||||
|
setApiServerLoading(false)
|
||||||
|
}
|
||||||
|
}, [apiServerLoading, checkApiServerStatus, t])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkApiServerStatus()
|
||||||
|
}, [checkApiServerStatus])
|
||||||
|
|
||||||
|
return {
|
||||||
|
apiServerConfig,
|
||||||
|
apiServerRunning,
|
||||||
|
apiServerLoading,
|
||||||
|
startApiServer,
|
||||||
|
stopApiServer,
|
||||||
|
restartApiServer,
|
||||||
|
checkApiServerStatus,
|
||||||
|
setApiServerEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -30,6 +30,11 @@
|
|||||||
"failed": "Failed to list agents."
|
"failed": "Failed to list agents."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"server": {
|
||||||
|
"error": {
|
||||||
|
"not_running": "The API server is enabled but not running properly."
|
||||||
|
}
|
||||||
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"accessible_paths": {
|
"accessible_paths": {
|
||||||
"add": "Add directory",
|
"add": "Add directory",
|
||||||
@ -237,6 +242,7 @@
|
|||||||
"messages": {
|
"messages": {
|
||||||
"apiKeyCopied": "API Key copied to clipboard",
|
"apiKeyCopied": "API Key copied to clipboard",
|
||||||
"apiKeyRegenerated": "API Key regenerated",
|
"apiKeyRegenerated": "API Key regenerated",
|
||||||
|
"notEnabled": "The API Server is not enabled.",
|
||||||
"operationFailed": "API Server operation failed: ",
|
"operationFailed": "API Server operation failed: ",
|
||||||
"restartError": "Failed to restart API Server: ",
|
"restartError": "Failed to restart API Server: ",
|
||||||
"restartFailed": "API Server restart failed: ",
|
"restartFailed": "API Server restart failed: ",
|
||||||
|
|||||||
@ -30,6 +30,11 @@
|
|||||||
"failed": "获取智能体列表失败"
|
"failed": "获取智能体列表失败"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"server": {
|
||||||
|
"error": {
|
||||||
|
"not_running": "API 服务器已启用但未正常运行。"
|
||||||
|
}
|
||||||
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"accessible_paths": {
|
"accessible_paths": {
|
||||||
"add": "添加目录",
|
"add": "添加目录",
|
||||||
@ -237,6 +242,7 @@
|
|||||||
"messages": {
|
"messages": {
|
||||||
"apiKeyCopied": "API 密钥已复制到剪贴板",
|
"apiKeyCopied": "API 密钥已复制到剪贴板",
|
||||||
"apiKeyRegenerated": "API 密钥已重新生成",
|
"apiKeyRegenerated": "API 密钥已重新生成",
|
||||||
|
"notEnabled": "API 服务器未启用。",
|
||||||
"operationFailed": "API 服务器操作失败:",
|
"operationFailed": "API 服务器操作失败:",
|
||||||
"restartError": "重启 API 服务器失败:",
|
"restartError": "重启 API 服务器失败:",
|
||||||
"restartFailed": "API 服务器重启失败:",
|
"restartFailed": "API 服务器重启失败:",
|
||||||
|
|||||||
@ -30,6 +30,11 @@
|
|||||||
"failed": "無法列出代理程式。"
|
"failed": "無法列出代理程式。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"server": {
|
||||||
|
"error": {
|
||||||
|
"not_running": "API 伺服器已啟用,但運行不正常。"
|
||||||
|
}
|
||||||
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"accessible_paths": {
|
"accessible_paths": {
|
||||||
"add": "新增目錄",
|
"add": "新增目錄",
|
||||||
@ -237,6 +242,7 @@
|
|||||||
"messages": {
|
"messages": {
|
||||||
"apiKeyCopied": "API 金鑰已複製到剪貼簿",
|
"apiKeyCopied": "API 金鑰已複製到剪貼簿",
|
||||||
"apiKeyRegenerated": "API 金鑰已重新生成",
|
"apiKeyRegenerated": "API 金鑰已重新生成",
|
||||||
|
"notEnabled": "API 伺服器未啟用。",
|
||||||
"operationFailed": "API 伺服器操作失敗:",
|
"operationFailed": "API 伺服器操作失敗:",
|
||||||
"restartError": "重新啟動 API 伺服器失敗:",
|
"restartError": "重新啟動 API 伺服器失敗:",
|
||||||
"restartFailed": "API 伺服器重新啟動失敗:",
|
"restartFailed": "API 伺服器重新啟動失敗:",
|
||||||
|
|||||||
@ -30,6 +30,11 @@
|
|||||||
"failed": "Αποτυχία καταχώρησης πρακτόρων."
|
"failed": "Αποτυχία καταχώρησης πρακτόρων."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"server": {
|
||||||
|
"error": {
|
||||||
|
"not_running": "Ο διακομιστής API είναι ενεργοποιημένος αλλά δεν λειτουργεί σωστά."
|
||||||
|
}
|
||||||
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"accessible_paths": {
|
"accessible_paths": {
|
||||||
"add": "Προσθήκη καταλόγου",
|
"add": "Προσθήκη καταλόγου",
|
||||||
@ -237,6 +242,7 @@
|
|||||||
"messages": {
|
"messages": {
|
||||||
"apiKeyCopied": "Το κλειδί API αντιγράφηκε στο πρόχειρο",
|
"apiKeyCopied": "Το κλειδί API αντιγράφηκε στο πρόχειρο",
|
||||||
"apiKeyRegenerated": "Το κλειδί API αναδημιουργήθηκε",
|
"apiKeyRegenerated": "Το κλειδί API αναδημιουργήθηκε",
|
||||||
|
"notEnabled": "Ο διακομιστής API δεν είναι ενεργοποιημένος.",
|
||||||
"operationFailed": "Η λειτουργία του Διακομιστή API απέτυχε: ",
|
"operationFailed": "Η λειτουργία του Διακομιστή API απέτυχε: ",
|
||||||
"restartError": "Αποτυχία επανεκκίνησης του Διακομιστή API: ",
|
"restartError": "Αποτυχία επανεκκίνησης του Διακομιστή API: ",
|
||||||
"restartFailed": "Η επανεκκίνηση του Διακομιστή API απέτυχε: ",
|
"restartFailed": "Η επανεκκίνηση του Διακομιστή API απέτυχε: ",
|
||||||
|
|||||||
@ -30,6 +30,11 @@
|
|||||||
"failed": "Error al listar agentes."
|
"failed": "Error al listar agentes."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"server": {
|
||||||
|
"error": {
|
||||||
|
"not_running": "El servidor de API está habilitado pero no funciona correctamente."
|
||||||
|
}
|
||||||
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"accessible_paths": {
|
"accessible_paths": {
|
||||||
"add": "Agregar directorio",
|
"add": "Agregar directorio",
|
||||||
@ -237,6 +242,7 @@
|
|||||||
"messages": {
|
"messages": {
|
||||||
"apiKeyCopied": "Clave API copiada al portapapeles",
|
"apiKeyCopied": "Clave API copiada al portapapeles",
|
||||||
"apiKeyRegenerated": "Clave API regenerada",
|
"apiKeyRegenerated": "Clave API regenerada",
|
||||||
|
"notEnabled": "El servidor de API no está habilitado.",
|
||||||
"operationFailed": "Falló la operación del Servidor API: ",
|
"operationFailed": "Falló la operación del Servidor API: ",
|
||||||
"restartError": "Error al reiniciar el Servidor API: ",
|
"restartError": "Error al reiniciar el Servidor API: ",
|
||||||
"restartFailed": "Falló el reinicio del Servidor API: ",
|
"restartFailed": "Falló el reinicio del Servidor API: ",
|
||||||
|
|||||||
@ -30,6 +30,11 @@
|
|||||||
"failed": "Échec de la liste des agents."
|
"failed": "Échec de la liste des agents."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"server": {
|
||||||
|
"error": {
|
||||||
|
"not_running": "Le serveur API est activé mais ne fonctionne pas correctement."
|
||||||
|
}
|
||||||
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"accessible_paths": {
|
"accessible_paths": {
|
||||||
"add": "Ajouter un répertoire",
|
"add": "Ajouter un répertoire",
|
||||||
@ -237,6 +242,7 @@
|
|||||||
"messages": {
|
"messages": {
|
||||||
"apiKeyCopied": "Clé API copiée dans le presse-papiers",
|
"apiKeyCopied": "Clé API copiée dans le presse-papiers",
|
||||||
"apiKeyRegenerated": "Clé API régénérée",
|
"apiKeyRegenerated": "Clé API régénérée",
|
||||||
|
"notEnabled": "Le serveur API n'est pas activé.",
|
||||||
"operationFailed": "Opération du Serveur API échouée : ",
|
"operationFailed": "Opération du Serveur API échouée : ",
|
||||||
"restartError": "Échec du redémarrage du Serveur API : ",
|
"restartError": "Échec du redémarrage du Serveur API : ",
|
||||||
"restartFailed": "Redémarrage du Serveur API échoué : ",
|
"restartFailed": "Redémarrage du Serveur API échoué : ",
|
||||||
|
|||||||
@ -30,6 +30,11 @@
|
|||||||
"failed": "エージェントの一覧取得に失敗しました。"
|
"failed": "エージェントの一覧取得に失敗しました。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"server": {
|
||||||
|
"error": {
|
||||||
|
"not_running": "APIサーバーは有効になっていますが、正常に動作していません。"
|
||||||
|
}
|
||||||
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"accessible_paths": {
|
"accessible_paths": {
|
||||||
"add": "ディレクトリを追加",
|
"add": "ディレクトリを追加",
|
||||||
@ -237,6 +242,7 @@
|
|||||||
"messages": {
|
"messages": {
|
||||||
"apiKeyCopied": "API キーがクリップボードにコピーされました",
|
"apiKeyCopied": "API キーがクリップボードにコピーされました",
|
||||||
"apiKeyRegenerated": "API キーが再生成されました",
|
"apiKeyRegenerated": "API キーが再生成されました",
|
||||||
|
"notEnabled": "APIサーバーが有効になっていません。",
|
||||||
"operationFailed": "API サーバーの操作に失敗しました:",
|
"operationFailed": "API サーバーの操作に失敗しました:",
|
||||||
"restartError": "API サーバーの再起動に失敗しました:",
|
"restartError": "API サーバーの再起動に失敗しました:",
|
||||||
"restartFailed": "API サーバーの再起動に失敗しました:",
|
"restartFailed": "API サーバーの再起動に失敗しました:",
|
||||||
|
|||||||
@ -30,6 +30,11 @@
|
|||||||
"failed": "Falha ao listar agentes."
|
"failed": "Falha ao listar agentes."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"server": {
|
||||||
|
"error": {
|
||||||
|
"not_running": "O servidor de API está habilitado, mas não está funcionando corretamente."
|
||||||
|
}
|
||||||
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"accessible_paths": {
|
"accessible_paths": {
|
||||||
"add": "Adicionar diretório",
|
"add": "Adicionar diretório",
|
||||||
@ -237,6 +242,7 @@
|
|||||||
"messages": {
|
"messages": {
|
||||||
"apiKeyCopied": "Chave API copiada para a área de transferência",
|
"apiKeyCopied": "Chave API copiada para a área de transferência",
|
||||||
"apiKeyRegenerated": "Chave API regenerada",
|
"apiKeyRegenerated": "Chave API regenerada",
|
||||||
|
"notEnabled": "O Servidor de API não está habilitado.",
|
||||||
"operationFailed": "Operação do Servidor API falhou: ",
|
"operationFailed": "Operação do Servidor API falhou: ",
|
||||||
"restartError": "Falha ao reiniciar o Servidor API: ",
|
"restartError": "Falha ao reiniciar o Servidor API: ",
|
||||||
"restartFailed": "Reinício do Servidor API falhou: ",
|
"restartFailed": "Reinício do Servidor API falhou: ",
|
||||||
|
|||||||
@ -30,6 +30,11 @@
|
|||||||
"failed": "Не удалось получить список агентов."
|
"failed": "Не удалось получить список агентов."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"server": {
|
||||||
|
"error": {
|
||||||
|
"not_running": "API-сервер включен, но работает неправильно."
|
||||||
|
}
|
||||||
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"accessible_paths": {
|
"accessible_paths": {
|
||||||
"add": "Добавить каталог",
|
"add": "Добавить каталог",
|
||||||
@ -237,6 +242,7 @@
|
|||||||
"messages": {
|
"messages": {
|
||||||
"apiKeyCopied": "API ключ скопирован в буфер обмена",
|
"apiKeyCopied": "API ключ скопирован в буфер обмена",
|
||||||
"apiKeyRegenerated": "API ключ перегенерирован",
|
"apiKeyRegenerated": "API ключ перегенерирован",
|
||||||
|
"notEnabled": "API-сервер не включен.",
|
||||||
"operationFailed": "Операция API сервера не удалась: ",
|
"operationFailed": "Операция API сервера не удалась: ",
|
||||||
"restartError": "Не удалось перезапустить API сервер: ",
|
"restartError": "Не удалось перезапустить API сервер: ",
|
||||||
"restartFailed": "Перезапуск API сервера не удался: ",
|
"restartFailed": "Перезапуск API сервера не удался: ",
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
import { Alert, Spinner } from '@heroui/react'
|
import { Alert, Spinner } from '@heroui/react'
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import { useAgents } from '@renderer/hooks/agents/useAgents'
|
import { useAgents } from '@renderer/hooks/agents/useAgents'
|
||||||
|
import { useApiServer } from '@renderer/hooks/useApiServer'
|
||||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||||
import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets'
|
import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useAssistantsTabSortType } from '@renderer/hooks/useStore'
|
import { useAssistantsTabSortType } from '@renderer/hooks/useStore'
|
||||||
import { useTags } from '@renderer/hooks/useTags'
|
import { useTags } from '@renderer/hooks/useTags'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { addIknowAction } from '@renderer/store/runtime'
|
import { addIknowAction } from '@renderer/store/runtime'
|
||||||
import { Assistant, AssistantsSortType } from '@renderer/types'
|
import { Assistant, AssistantsSortType } from '@renderer/types'
|
||||||
|
import { getErrorMessage } from '@renderer/utils'
|
||||||
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -35,7 +36,8 @@ const AssistantsTab: FC<AssistantsTabProps> = (props) => {
|
|||||||
const { activeAssistant, setActiveAssistant, onCreateAssistant, onCreateDefaultAssistant } = props
|
const { activeAssistant, setActiveAssistant, onCreateAssistant, onCreateDefaultAssistant } = props
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { apiServer } = useSettings()
|
const { apiServerConfig, apiServerRunning } = useApiServer()
|
||||||
|
const apiServerEnabled = apiServerConfig.enabled
|
||||||
const { iknow, chat } = useRuntime()
|
const { iknow, chat } = useRuntime()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
@ -55,7 +57,7 @@ const AssistantsTab: FC<AssistantsTabProps> = (props) => {
|
|||||||
const { unifiedItems, handleUnifiedListReorder } = useUnifiedItems({
|
const { unifiedItems, handleUnifiedListReorder } = useUnifiedItems({
|
||||||
agents,
|
agents,
|
||||||
assistants,
|
assistants,
|
||||||
apiServerEnabled: apiServer.enabled,
|
apiServerEnabled,
|
||||||
agentsLoading,
|
agentsLoading,
|
||||||
agentsError,
|
agentsError,
|
||||||
updateAssistants
|
updateAssistants
|
||||||
@ -72,17 +74,17 @@ const AssistantsTab: FC<AssistantsTabProps> = (props) => {
|
|||||||
unifiedItems,
|
unifiedItems,
|
||||||
assistants,
|
assistants,
|
||||||
agents,
|
agents,
|
||||||
apiServerEnabled: apiServer.enabled,
|
apiServerEnabled,
|
||||||
agentsLoading,
|
agentsLoading,
|
||||||
agentsError,
|
agentsError,
|
||||||
updateAssistants
|
updateAssistants
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!agentsLoading && agents.length > 0 && !activeAgentId && apiServer.enabled) {
|
if (!agentsLoading && agents.length > 0 && !activeAgentId && apiServerConfig.enabled) {
|
||||||
setActiveAgentId(agents[0].id)
|
setActiveAgentId(agents[0].id)
|
||||||
}
|
}
|
||||||
}, [agentsLoading, agents, activeAgentId, setActiveAgentId, apiServer.enabled])
|
}, [agentsLoading, agents, activeAgentId, setActiveAgentId, apiServerConfig.enabled])
|
||||||
|
|
||||||
const onDeleteAssistant = useCallback(
|
const onDeleteAssistant = useCallback(
|
||||||
(assistant: Assistant) => {
|
(assistant: Assistant) => {
|
||||||
@ -105,7 +107,7 @@ const AssistantsTab: FC<AssistantsTabProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="assistants-tab" ref={containerRef}>
|
<Container className="assistants-tab" ref={containerRef}>
|
||||||
{!apiServer.enabled && !iknow[ALERT_KEY] && (
|
{!apiServerConfig.enabled && !iknow[ALERT_KEY] && (
|
||||||
<Alert
|
<Alert
|
||||||
color="warning"
|
color="warning"
|
||||||
title={t('agent.warning.enable_server')}
|
title={t('agent.warning.enable_server')}
|
||||||
@ -113,11 +115,22 @@ const AssistantsTab: FC<AssistantsTabProps> = (props) => {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
dispatch(addIknowAction(ALERT_KEY))
|
dispatch(addIknowAction(ALERT_KEY))
|
||||||
}}
|
}}
|
||||||
|
className="mb-2"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{agentsLoading && <Spinner />}
|
{agentsLoading && <Spinner />}
|
||||||
{apiServer.enabled && agentsError && <Alert color="danger" title={t('agent.list.error.failed')} />}
|
{apiServerConfig.enabled && !apiServerRunning && (
|
||||||
|
<Alert color="danger" title={t('agent.server.error.not_running')} isClosable className="mb-2" />
|
||||||
|
)}
|
||||||
|
{apiServerConfig.enabled && apiServerRunning && agentsError && (
|
||||||
|
<Alert
|
||||||
|
color="danger"
|
||||||
|
title={t('agent.list.error.failed')}
|
||||||
|
description={getErrorMessage(agentsError)}
|
||||||
|
className="mb-2"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{assistantsTabSortType === 'tags' ? (
|
{assistantsTabSortType === 'tags' ? (
|
||||||
<UnifiedTagGroups
|
<UnifiedTagGroups
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
// TODO: Refactor this component to use HeroUI
|
// TODO: Refactor this component to use HeroUI
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { loggerService } from '@renderer/services/LoggerService'
|
import { useApiServer } from '@renderer/hooks/useApiServer'
|
||||||
import { RootState, useAppDispatch } from '@renderer/store'
|
import { RootState, useAppDispatch } from '@renderer/store'
|
||||||
import { setApiServerApiKey, setApiServerEnabled, setApiServerPort } from '@renderer/store/settings'
|
import { setApiServerApiKey, setApiServerPort } from '@renderer/store/settings'
|
||||||
import { formatErrorMessage } from '@renderer/utils/error'
|
import { formatErrorMessage } from '@renderer/utils/error'
|
||||||
import { IpcChannel } from '@shared/IpcChannel'
|
|
||||||
import { Button, Input, InputNumber, Tooltip, Typography } from 'antd'
|
import { Button, Input, InputNumber, Tooltip, Typography } from 'antd'
|
||||||
import { Copy, ExternalLink, Play, RotateCcw, Square } from 'lucide-react'
|
import { Copy, ExternalLink, Play, RotateCcw, Square } from 'lucide-react'
|
||||||
import { FC, useEffect, useState } from 'react'
|
import { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -15,7 +14,6 @@ import { v4 as uuidv4 } from 'uuid'
|
|||||||
|
|
||||||
import { SettingContainer } from '../..'
|
import { SettingContainer } from '../..'
|
||||||
|
|
||||||
const logger = loggerService.withContext('ApiServerSettings')
|
|
||||||
const { Text, Title } = Typography
|
const { Text, Title } = Typography
|
||||||
|
|
||||||
const ApiServerSettings: FC = () => {
|
const ApiServerSettings: FC = () => {
|
||||||
@ -25,67 +23,25 @@ const ApiServerSettings: FC = () => {
|
|||||||
|
|
||||||
// API Server state with proper defaults
|
// API Server state with proper defaults
|
||||||
const apiServerConfig = useSelector((state: RootState) => state.settings.apiServer)
|
const apiServerConfig = useSelector((state: RootState) => state.settings.apiServer)
|
||||||
|
const { apiServerRunning, apiServerLoading, startApiServer, stopApiServer, restartApiServer, setApiServerEnabled } =
|
||||||
const [apiServerRunning, setApiServerRunning] = useState(false)
|
useApiServer()
|
||||||
const [apiServerLoading, setApiServerLoading] = useState(false)
|
|
||||||
|
|
||||||
// API Server functions
|
|
||||||
const checkApiServerStatus = async () => {
|
|
||||||
try {
|
|
||||||
const status = await window.electron.ipcRenderer.invoke(IpcChannel.ApiServer_GetStatus)
|
|
||||||
setApiServerRunning(status.running)
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error('Failed to check API server status:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
checkApiServerStatus()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleApiServerToggle = async (enabled: boolean) => {
|
const handleApiServerToggle = async (enabled: boolean) => {
|
||||||
setApiServerLoading(true)
|
|
||||||
try {
|
try {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
const result = await window.electron.ipcRenderer.invoke(IpcChannel.ApiServer_Start)
|
await startApiServer()
|
||||||
if (result.success) {
|
|
||||||
setApiServerRunning(true)
|
|
||||||
window.toast.success(t('apiServer.messages.startSuccess'))
|
|
||||||
} else {
|
|
||||||
window.toast.error(t('apiServer.messages.startError') + result.error)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const result = await window.electron.ipcRenderer.invoke(IpcChannel.ApiServer_Stop)
|
await stopApiServer()
|
||||||
if (result.success) {
|
|
||||||
setApiServerRunning(false)
|
|
||||||
window.toast.success(t('apiServer.messages.stopSuccess'))
|
|
||||||
} else {
|
|
||||||
window.toast.error(t('apiServer.messages.stopError') + result.error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.toast.error(t('apiServer.messages.operationFailed') + formatErrorMessage(error))
|
window.toast.error(t('apiServer.messages.operationFailed') + formatErrorMessage(error))
|
||||||
} finally {
|
} finally {
|
||||||
dispatch(setApiServerEnabled(enabled))
|
setApiServerEnabled(enabled)
|
||||||
setApiServerLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleApiServerRestart = async () => {
|
const handleApiServerRestart = async () => {
|
||||||
setApiServerLoading(true)
|
await restartApiServer()
|
||||||
try {
|
|
||||||
const result = await window.electron.ipcRenderer.invoke(IpcChannel.ApiServer_Restart)
|
|
||||||
if (result.success) {
|
|
||||||
await checkApiServerStatus()
|
|
||||||
window.toast.success(t('apiServer.messages.restartSuccess'))
|
|
||||||
} else {
|
|
||||||
window.toast.error(t('apiServer.messages.restartError') + result.error)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
window.toast.error(t('apiServer.messages.restartFailed') + (error as Error).message)
|
|
||||||
} finally {
|
|
||||||
setApiServerLoading(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyApiKey = () => {
|
const copyApiKey = () => {
|
||||||
|
|||||||
38
src/renderer/src/types/apiServer.ts
Normal file
38
src/renderer/src/types/apiServer.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
export type ApiServerConfig = {
|
||||||
|
enabled: boolean
|
||||||
|
host: string
|
||||||
|
port: number
|
||||||
|
apiKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetApiServerStatusResult = {
|
||||||
|
running: boolean
|
||||||
|
config: ApiServerConfig | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StartApiServerStatusResult =
|
||||||
|
| {
|
||||||
|
success: true
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false
|
||||||
|
error: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RestartApiServerStatusResult =
|
||||||
|
| {
|
||||||
|
success: true
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false
|
||||||
|
error: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StopApiServerStatusResult =
|
||||||
|
| {
|
||||||
|
success: true
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false
|
||||||
|
error: string
|
||||||
|
}
|
||||||
@ -17,6 +17,7 @@ import type { BaseTool, MCPTool } from './tool'
|
|||||||
|
|
||||||
export * from './agent'
|
export * from './agent'
|
||||||
export * from './apiModels'
|
export * from './apiModels'
|
||||||
|
export * from './apiServer'
|
||||||
export * from './knowledge'
|
export * from './knowledge'
|
||||||
export * from './mcp'
|
export * from './mcp'
|
||||||
export * from './notification'
|
export * from './notification'
|
||||||
@ -848,13 +849,6 @@ export type S3Config = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type { Message } from './newMessage'
|
export type { Message } from './newMessage'
|
||||||
|
|
||||||
export interface ApiServerConfig {
|
|
||||||
enabled: boolean
|
|
||||||
host: string
|
|
||||||
port: number
|
|
||||||
apiKey: string
|
|
||||||
}
|
|
||||||
export * from './tool'
|
export * from './tool'
|
||||||
|
|
||||||
// Memory Service Types
|
// Memory Service Types
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user