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 { 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<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');
return res.json();
}
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');
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<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');
return res.json();
}
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',
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<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');
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<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');
return res.json();
}
async function installCopilotApi(version?: string): Promise<CopilotInstallResult> {
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 } : {}),
+12 -11
View File
@@ -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<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');
return res.json();
}
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');
return res.json();
}
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');
return res.json();
}
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');
return res.json();
}
@@ -85,7 +86,7 @@ async function fetchCursorRawSettings(): Promise<CursorRawSettings> {
async function updateCursorConfig(
updates: Partial<CursorConfig>
): 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<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) {
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<CursorAuthResult> {
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();
}
+6 -2
View File
@@ -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<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' },
...options,
});