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
This commit is contained in:
Tam Nhu Tran
2026-02-18 03:10:09 +07:00
parent 63f422179e
commit feb556dc90
3 changed files with 30 additions and 26 deletions
+12 -13
View File
@@ -6,8 +6,7 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { withApiBase } from '@/lib/api-client';
const API_BASE = '/api';
// Types // Types
export interface CopilotStatus { export interface CopilotStatus {
@@ -80,31 +79,31 @@ export interface CopilotRawSettings {
// API functions // API functions
async function fetchCopilotStatus(): Promise<CopilotStatus> { async function fetchCopilotStatus(): Promise<CopilotStatus> {
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'); if (!res.ok) throw new Error('Failed to fetch copilot status');
return res.json(); return res.json();
} }
async function fetchCopilotConfig(): Promise<CopilotConfig> { async function fetchCopilotConfig(): Promise<CopilotConfig> {
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'); if (!res.ok) throw new Error('Failed to fetch copilot config');
return res.json(); return res.json();
} }
async function fetchCopilotModels(): Promise<{ models: CopilotModel[]; current: string }> { 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'); if (!res.ok) throw new Error('Failed to fetch copilot models');
return res.json(); return res.json();
} }
async function fetchCopilotRawSettings(): Promise<CopilotRawSettings> { async function fetchCopilotRawSettings(): Promise<CopilotRawSettings> {
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'); if (!res.ok) throw new Error('Failed to fetch copilot raw settings');
return res.json(); return res.json();
} }
async function updateCopilotConfig(config: Partial<CopilotConfig>): Promise<{ success: boolean }> { async function updateCopilotConfig(config: Partial<CopilotConfig>): Promise<{ success: boolean }> {
const res = await fetch(`${API_BASE}/copilot/config`, { const res = await fetch(withApiBase('/copilot/config'), {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config), body: JSON.stringify(config),
@@ -117,7 +116,7 @@ async function saveCopilotRawSettings(data: {
settings: CopilotRawSettings['settings']; settings: CopilotRawSettings['settings'];
expectedMtime?: number; expectedMtime?: number;
}): Promise<{ success: boolean; mtime: 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', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data), body: JSON.stringify(data),
@@ -135,31 +134,31 @@ export interface CopilotAuthResult {
} }
async function startCopilotAuth(): Promise<CopilotAuthResult> { async function startCopilotAuth(): Promise<CopilotAuthResult> {
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'); if (!res.ok) throw new Error('Failed to start auth');
return res.json(); return res.json();
} }
async function startCopilotDaemon(): Promise<{ success: boolean; pid?: number; error?: string }> { 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'); if (!res.ok) throw new Error('Failed to start daemon');
return res.json(); return res.json();
} }
async function stopCopilotDaemon(): Promise<{ success: boolean; error?: string }> { 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'); if (!res.ok) throw new Error('Failed to stop daemon');
return res.json(); return res.json();
} }
async function fetchCopilotInfo(): Promise<CopilotInfo> { async function fetchCopilotInfo(): Promise<CopilotInfo> {
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'); if (!res.ok) throw new Error('Failed to fetch copilot info');
return res.json(); return res.json();
} }
async function installCopilotApi(version?: string): Promise<CopilotInstallResult> { async function installCopilotApi(version?: string): Promise<CopilotInstallResult> {
const res = await fetch(`${API_BASE}/copilot/install`, { const res = await fetch(withApiBase('/copilot/install'), {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(version ? { version } : {}), body: JSON.stringify(version ? { version } : {}),
+12 -11
View File
@@ -6,8 +6,9 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; 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 { export interface CursorStatus {
enabled: boolean; enabled: boolean;
@@ -59,25 +60,25 @@ interface CursorAuthResult {
} }
async function fetchCursorStatus(): Promise<CursorStatus> { async function fetchCursorStatus(): Promise<CursorStatus> {
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'); if (!res.ok) throw new Error('Failed to fetch cursor status');
return res.json(); return res.json();
} }
async function fetchCursorConfig(): Promise<CursorConfig> { async function fetchCursorConfig(): Promise<CursorConfig> {
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'); if (!res.ok) throw new Error('Failed to fetch cursor config');
return res.json(); return res.json();
} }
async function fetchCursorModels(): Promise<CursorModelsResponse> { async function fetchCursorModels(): Promise<CursorModelsResponse> {
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'); if (!res.ok) throw new Error('Failed to fetch cursor models');
return res.json(); return res.json();
} }
async function fetchCursorRawSettings(): Promise<CursorRawSettings> { async function fetchCursorRawSettings(): Promise<CursorRawSettings> {
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'); if (!res.ok) throw new Error('Failed to fetch cursor raw settings');
return res.json(); return res.json();
} }
@@ -85,7 +86,7 @@ async function fetchCursorRawSettings(): Promise<CursorRawSettings> {
async function updateCursorConfig( async function updateCursorConfig(
updates: Partial<CursorConfig> updates: Partial<CursorConfig>
): Promise<{ success: boolean; cursor: CursorConfig }> { ): Promise<{ success: boolean; cursor: CursorConfig }> {
const res = await fetch(`${API_BASE}/cursor/settings`, { const res = await fetch(withApiBase('/cursor/settings'), {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates), body: JSON.stringify(updates),
@@ -98,7 +99,7 @@ async function saveCursorRawSettings(data: {
settings: CursorRawSettings['settings']; settings: CursorRawSettings['settings'];
expectedMtime?: number; expectedMtime?: number;
}): Promise<{ success: boolean; mtime: 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', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data), body: JSON.stringify(data),
@@ -109,7 +110,7 @@ async function saveCursorRawSettings(data: {
} }
async function autoDetectCursorAuth(): Promise<CursorAuthResult> { async function autoDetectCursorAuth(): Promise<CursorAuthResult> {
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) { if (!res.ok) {
const error = await res.json().catch(() => ({ error: 'Auto-detect failed' })); const error = await res.json().catch(() => ({ error: 'Auto-detect failed' }));
throw new Error(error.error || 'Auto-detect failed'); throw new Error(error.error || 'Auto-detect failed');
@@ -121,7 +122,7 @@ async function importCursorAuthManual(data: {
accessToken: string; accessToken: string;
machineId: string; machineId: string;
}): Promise<CursorAuthResult> { }): Promise<CursorAuthResult> {
const res = await fetch(`${API_BASE}/cursor/auth/import`, { const res = await fetch(withApiBase('/cursor/auth/import'), {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data), body: JSON.stringify(data),
@@ -134,13 +135,13 @@ async function importCursorAuthManual(data: {
} }
async function startCursorDaemon(): Promise<{ success: boolean; pid?: number; error?: string }> { 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'); if (!res.ok) throw new Error('Failed to start cursor daemon');
return res.json(); return res.json();
} }
async function stopCursorDaemon(): Promise<{ success: boolean; error?: string }> { 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'); if (!res.ok) throw new Error('Failed to stop cursor daemon');
return res.json(); return res.json();
} }
+6 -2
View File
@@ -5,10 +5,14 @@
import type { CLIProxyProvider } from './provider-config'; 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<T>(url: string, options?: RequestInit): Promise<T> { async function request<T>(url: string, options?: RequestInit): Promise<T> {
const res = await fetch(`${BASE_URL}${url}`, { const res = await fetch(withApiBase(url), {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
...options, ...options,
}); });