cc00a439bf
Add a dedicated Deep Research view with postal-area preview overlays, batch execution, and bundled map results. Also add postal dataset import tooling and fix local API networking and research insert issues needed to support the new workflow.
73 lines
2.0 KiB
TypeScript
73 lines
2.0 KiB
TypeScript
function isLoopbackHostname(hostname: string) {
|
|
return hostname === 'localhost' || hostname === '127.0.0.1';
|
|
}
|
|
|
|
function normalizeBaseUrl(baseUrl: string) {
|
|
return baseUrl.replace(/\/+$/, '');
|
|
}
|
|
|
|
function resolveApiBaseUrl() {
|
|
const configuredBaseUrl = (import.meta.env.VITE_API_BASE_URL ?? '').trim();
|
|
|
|
if (typeof window === 'undefined') {
|
|
return normalizeBaseUrl(configuredBaseUrl);
|
|
}
|
|
|
|
const fallbackBaseUrl = `${window.location.protocol}//${window.location.hostname}:4000/api`;
|
|
|
|
if (!configuredBaseUrl) {
|
|
return fallbackBaseUrl;
|
|
}
|
|
|
|
try {
|
|
const configuredUrl = new URL(configuredBaseUrl);
|
|
|
|
if (isLoopbackHostname(configuredUrl.hostname) && !isLoopbackHostname(window.location.hostname)) {
|
|
configuredUrl.hostname = window.location.hostname;
|
|
return normalizeBaseUrl(configuredUrl.toString());
|
|
}
|
|
|
|
return normalizeBaseUrl(configuredUrl.toString());
|
|
} catch {
|
|
return normalizeBaseUrl(configuredBaseUrl);
|
|
}
|
|
}
|
|
|
|
const apiBaseUrl = resolveApiBaseUrl();
|
|
|
|
export const hasApiConfig = Boolean(apiBaseUrl);
|
|
|
|
export async function apiRequest<T>(path: string, init?: RequestInit): Promise<T> {
|
|
if (!apiBaseUrl) {
|
|
throw new Error('VITE_API_BASE_URL is not configured.');
|
|
}
|
|
|
|
let response: Response;
|
|
|
|
try {
|
|
response = await fetch(`${apiBaseUrl}${path}`, {
|
|
credentials: 'include',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(init?.headers ?? {}),
|
|
},
|
|
...init,
|
|
});
|
|
} catch (error) {
|
|
throw new Error(
|
|
`Failed to reach the local API at ${apiBaseUrl}. Check that the API server is running and that VITE_API_BASE_URL matches the host you opened in the browser.`,
|
|
{ cause: error },
|
|
);
|
|
}
|
|
|
|
const contentType = response.headers.get('content-type') || '';
|
|
const payload = contentType.includes('application/json') ? ((await response.json()) as unknown) : null;
|
|
|
|
if (!response.ok) {
|
|
const message = payload && typeof payload === 'object' && 'error' in payload ? String(payload.error) : 'Request failed.';
|
|
throw new Error(message);
|
|
}
|
|
|
|
return payload as T;
|
|
}
|