From feb556dc90dab593e79aaf6145dd5b9a38eb6831 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 18 Feb 2026 03:07:23 +0700 Subject: [PATCH] refactor(ui): unify api base path for cursor and copilot hooks - add withApiBase helper and use it across fetch calls - remove duplicated API base literals in cursor/copilot hooks - expose cursor default port from shared ui defaults module --- ui/src/hooks/use-copilot.ts | 25 ++++++++++++------------- ui/src/hooks/use-cursor.ts | 23 ++++++++++++----------- ui/src/lib/api-client.ts | 8 ++++++-- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/ui/src/hooks/use-copilot.ts b/ui/src/hooks/use-copilot.ts index 2aed765e..06e8c84d 100644 --- a/ui/src/hooks/use-copilot.ts +++ b/ui/src/hooks/use-copilot.ts @@ -6,8 +6,7 @@ import { useMemo } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; - -const API_BASE = '/api'; +import { withApiBase } from '@/lib/api-client'; // Types export interface CopilotStatus { @@ -80,31 +79,31 @@ export interface CopilotRawSettings { // API functions async function fetchCopilotStatus(): Promise { - const res = await fetch(`${API_BASE}/copilot/status`); + const res = await fetch(withApiBase('/copilot/status')); if (!res.ok) throw new Error('Failed to fetch copilot status'); return res.json(); } async function fetchCopilotConfig(): Promise { - const res = await fetch(`${API_BASE}/copilot/config`); + const res = await fetch(withApiBase('/copilot/config')); if (!res.ok) throw new Error('Failed to fetch copilot config'); return res.json(); } async function fetchCopilotModels(): Promise<{ models: CopilotModel[]; current: string }> { - const res = await fetch(`${API_BASE}/copilot/models`); + const res = await fetch(withApiBase('/copilot/models')); if (!res.ok) throw new Error('Failed to fetch copilot models'); return res.json(); } async function fetchCopilotRawSettings(): Promise { - const res = await fetch(`${API_BASE}/copilot/settings/raw`); + const res = await fetch(withApiBase('/copilot/settings/raw')); if (!res.ok) throw new Error('Failed to fetch copilot raw settings'); return res.json(); } async function updateCopilotConfig(config: Partial): Promise<{ success: boolean }> { - const res = await fetch(`${API_BASE}/copilot/config`, { + const res = await fetch(withApiBase('/copilot/config'), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config), @@ -117,7 +116,7 @@ async function saveCopilotRawSettings(data: { settings: CopilotRawSettings['settings']; expectedMtime?: number; }): Promise<{ success: boolean; mtime: number }> { - const res = await fetch(`${API_BASE}/copilot/settings/raw`, { + const res = await fetch(withApiBase('/copilot/settings/raw'), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), @@ -135,31 +134,31 @@ export interface CopilotAuthResult { } async function startCopilotAuth(): Promise { - const res = await fetch(`${API_BASE}/copilot/auth/start`, { method: 'POST' }); + const res = await fetch(withApiBase('/copilot/auth/start'), { method: 'POST' }); if (!res.ok) throw new Error('Failed to start auth'); return res.json(); } async function startCopilotDaemon(): Promise<{ success: boolean; pid?: number; error?: string }> { - const res = await fetch(`${API_BASE}/copilot/daemon/start`, { method: 'POST' }); + const res = await fetch(withApiBase('/copilot/daemon/start'), { method: 'POST' }); if (!res.ok) throw new Error('Failed to start daemon'); return res.json(); } async function stopCopilotDaemon(): Promise<{ success: boolean; error?: string }> { - const res = await fetch(`${API_BASE}/copilot/daemon/stop`, { method: 'POST' }); + const res = await fetch(withApiBase('/copilot/daemon/stop'), { method: 'POST' }); if (!res.ok) throw new Error('Failed to stop daemon'); return res.json(); } async function fetchCopilotInfo(): Promise { - const res = await fetch(`${API_BASE}/copilot/info`); + const res = await fetch(withApiBase('/copilot/info')); if (!res.ok) throw new Error('Failed to fetch copilot info'); return res.json(); } async function installCopilotApi(version?: string): Promise { - const res = await fetch(`${API_BASE}/copilot/install`, { + const res = await fetch(withApiBase('/copilot/install'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(version ? { version } : {}), diff --git a/ui/src/hooks/use-cursor.ts b/ui/src/hooks/use-cursor.ts index 20ed9be8..e86d8782 100644 --- a/ui/src/hooks/use-cursor.ts +++ b/ui/src/hooks/use-cursor.ts @@ -6,8 +6,9 @@ import { useMemo } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { withApiBase } from '@/lib/api-client'; -const API_BASE = '/api'; +export { DEFAULT_CURSOR_PORT } from '@/lib/default-ports'; export interface CursorStatus { enabled: boolean; @@ -59,25 +60,25 @@ interface CursorAuthResult { } async function fetchCursorStatus(): Promise { - const res = await fetch(`${API_BASE}/cursor/status`); + const res = await fetch(withApiBase('/cursor/status')); if (!res.ok) throw new Error('Failed to fetch cursor status'); return res.json(); } async function fetchCursorConfig(): Promise { - const res = await fetch(`${API_BASE}/cursor/settings`); + const res = await fetch(withApiBase('/cursor/settings')); if (!res.ok) throw new Error('Failed to fetch cursor config'); return res.json(); } async function fetchCursorModels(): Promise { - const res = await fetch(`${API_BASE}/cursor/models`); + const res = await fetch(withApiBase('/cursor/models')); if (!res.ok) throw new Error('Failed to fetch cursor models'); return res.json(); } async function fetchCursorRawSettings(): Promise { - const res = await fetch(`${API_BASE}/cursor/settings/raw`); + const res = await fetch(withApiBase('/cursor/settings/raw')); if (!res.ok) throw new Error('Failed to fetch cursor raw settings'); return res.json(); } @@ -85,7 +86,7 @@ async function fetchCursorRawSettings(): Promise { async function updateCursorConfig( updates: Partial ): Promise<{ success: boolean; cursor: CursorConfig }> { - const res = await fetch(`${API_BASE}/cursor/settings`, { + const res = await fetch(withApiBase('/cursor/settings'), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updates), @@ -98,7 +99,7 @@ async function saveCursorRawSettings(data: { settings: CursorRawSettings['settings']; expectedMtime?: number; }): Promise<{ success: boolean; mtime: number }> { - const res = await fetch(`${API_BASE}/cursor/settings/raw`, { + const res = await fetch(withApiBase('/cursor/settings/raw'), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), @@ -109,7 +110,7 @@ async function saveCursorRawSettings(data: { } async function autoDetectCursorAuth(): Promise { - const res = await fetch(`${API_BASE}/cursor/auth/auto-detect`, { method: 'POST' }); + const res = await fetch(withApiBase('/cursor/auth/auto-detect'), { method: 'POST' }); if (!res.ok) { const error = await res.json().catch(() => ({ error: 'Auto-detect failed' })); throw new Error(error.error || 'Auto-detect failed'); @@ -121,7 +122,7 @@ async function importCursorAuthManual(data: { accessToken: string; machineId: string; }): Promise { - const res = await fetch(`${API_BASE}/cursor/auth/import`, { + const res = await fetch(withApiBase('/cursor/auth/import'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), @@ -134,13 +135,13 @@ async function importCursorAuthManual(data: { } async function startCursorDaemon(): Promise<{ success: boolean; pid?: number; error?: string }> { - const res = await fetch(`${API_BASE}/cursor/daemon/start`, { method: 'POST' }); + const res = await fetch(withApiBase('/cursor/daemon/start'), { method: 'POST' }); if (!res.ok) throw new Error('Failed to start cursor daemon'); return res.json(); } async function stopCursorDaemon(): Promise<{ success: boolean; error?: string }> { - const res = await fetch(`${API_BASE}/cursor/daemon/stop`, { method: 'POST' }); + const res = await fetch(withApiBase('/cursor/daemon/stop'), { method: 'POST' }); if (!res.ok) throw new Error('Failed to stop cursor daemon'); return res.json(); } diff --git a/ui/src/lib/api-client.ts b/ui/src/lib/api-client.ts index 9f01191f..7b28b20c 100644 --- a/ui/src/lib/api-client.ts +++ b/ui/src/lib/api-client.ts @@ -5,10 +5,14 @@ import type { CLIProxyProvider } from './provider-config'; -const BASE_URL = '/api'; +export const API_BASE_URL = '/api'; + +export function withApiBase(path: string): string { + return `${API_BASE_URL}${path}`; +} async function request(url: string, options?: RequestInit): Promise { - const res = await fetch(`${BASE_URL}${url}`, { + const res = await fetch(withApiBase(url), { headers: { 'Content-Type': 'application/json' }, ...options, });