diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index a8421354f..9a3866d48 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -322,6 +322,7 @@ export enum IpcChannel { ApiServer_Stop = 'api-server:stop', ApiServer_Restart = 'api-server:restart', ApiServer_GetStatus = 'api-server:get-status', + ApiServer_Ready = 'api-server:ready', // NOTE: This api is not be used. ApiServer_GetConfig = 'api-server:get-config', diff --git a/src/main/apiServer/server.ts b/src/main/apiServer/server.ts index 3cb81f412..9b15e56da 100644 --- a/src/main/apiServer/server.ts +++ b/src/main/apiServer/server.ts @@ -1,8 +1,10 @@ import { createServer } from 'node:http' import { loggerService } from '@logger' +import { IpcChannel } from '@shared/IpcChannel' import { agentService } from '../services/agents' +import { windowService } from '../services/WindowService' import { app } from './app' import { config } from './config' @@ -43,6 +45,13 @@ export class ApiServer { return new Promise((resolve, reject) => { this.server!.listen(port, host, () => { logger.info('API server started', { host, port }) + + // Notify renderer that API server is ready + const mainWindow = windowService.getMainWindow() + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send(IpcChannel.ApiServer_Ready) + } + resolve() }) diff --git a/src/preload/index.ts b/src/preload/index.ts index 12aa9fd3b..fa39ac698 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -525,7 +525,16 @@ const api = { getStatus: (): Promise => ipcRenderer.invoke(IpcChannel.ApiServer_GetStatus), start: (): Promise => ipcRenderer.invoke(IpcChannel.ApiServer_Start), restart: (): Promise => ipcRenderer.invoke(IpcChannel.ApiServer_Restart), - stop: (): Promise => ipcRenderer.invoke(IpcChannel.ApiServer_Stop) + stop: (): Promise => ipcRenderer.invoke(IpcChannel.ApiServer_Stop), + onReady: (callback: () => void): (() => void) => { + const listener = () => { + callback() + } + ipcRenderer.on(IpcChannel.ApiServer_Ready, listener) + return () => { + ipcRenderer.removeListener(IpcChannel.ApiServer_Ready, listener) + } + } }, claudeCodePlugin: { listAvailable: (): Promise> => diff --git a/src/renderer/src/hooks/agents/useAgents.ts b/src/renderer/src/hooks/agents/useAgents.ts index f14b89351..3f5dcb6e3 100644 --- a/src/renderer/src/hooks/agents/useAgents.ts +++ b/src/renderer/src/hooks/agents/useAgents.ts @@ -25,6 +25,10 @@ export const useAgents = () => { const client = useAgentClient() const key = client.agentPaths.base const { apiServerConfig, apiServerRunning } = useApiServer() + + // Disable SWR fetching when server is not running by setting key to null + const swrKey = apiServerRunning ? key : null + const fetcher = useCallback(async () => { // API server will start on startup if enabled OR there are agents if (!apiServerConfig.enabled && !apiServerRunning) { @@ -37,7 +41,7 @@ export const useAgents = () => { // NOTE: We only use the array for now. useUpdateAgent depends on this behavior. return result.data }, [apiServerConfig.enabled, apiServerRunning, client, t]) - const { data, error, isLoading, mutate } = useSWR(key, fetcher) + const { data, error, isLoading, mutate } = useSWR(swrKey, fetcher) const { chat } = useRuntime() const { activeAgentId } = chat const dispatch = useAppDispatch() diff --git a/src/renderer/src/hooks/useApiServer.ts b/src/renderer/src/hooks/useApiServer.ts index ae418f6cc..38f6fa64d 100644 --- a/src/renderer/src/hooks/useApiServer.ts +++ b/src/renderer/src/hooks/useApiServer.ts @@ -14,8 +14,8 @@ export const useApiServer = () => { const apiServerConfig = useAppSelector((state) => state.settings.apiServer) const dispatch = useAppDispatch() - // Optimistic initial state. - const [apiServerRunning, setApiServerRunning] = useState(apiServerConfig.enabled) + // Initial state - no longer optimistic, wait for actual status + const [apiServerRunning, setApiServerRunning] = useState(false) const [apiServerLoading, setApiServerLoading] = useState(true) const setApiServerEnabled = useCallback( @@ -99,6 +99,16 @@ export const useApiServer = () => { checkApiServerStatus() }, [checkApiServerStatus]) + // Listen for API server ready event + useEffect(() => { + const cleanup = window.api.apiServer.onReady(() => { + logger.info('API server ready event received, checking status') + checkApiServerStatus() + }) + + return cleanup + }, [checkApiServerStatus]) + return { apiServerConfig, apiServerRunning, diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx index b0c901851..8fc439b7b 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx @@ -36,7 +36,7 @@ const AssistantsTab: FC = (props) => { const { activeAssistant, setActiveAssistant, onCreateAssistant, onCreateDefaultAssistant } = props const containerRef = useRef(null) const { t } = useTranslation() - const { apiServerConfig, apiServerRunning } = useApiServer() + const { apiServerConfig, apiServerRunning, apiServerLoading } = useApiServer() const apiServerEnabled = apiServerConfig.enabled const { iknow, chat } = useRuntime() const dispatch = useAppDispatch() @@ -113,8 +113,8 @@ const AssistantsTab: FC = (props) => { /> )} - {agentsLoading && } - {apiServerConfig.enabled && !apiServerRunning && ( + {(agentsLoading || apiServerLoading) && } + {apiServerConfig.enabled && !apiServerLoading && !apiServerRunning && ( )} {apiServerRunning && agentsError && (