akhaliq's picture
akhaliq HF Staff
add import feature
3eb00a5
raw
history blame
6.87 kB
// API client for AnyCoder backend
import axios, { AxiosInstance } from 'axios';
import type {
Model,
AuthStatus,
CodeGenerationRequest,
DeploymentRequest,
DeploymentResponse,
Language,
} from '@/types';
// Use relative URLs in production (Next.js rewrites will proxy to backend)
// In local dev, use localhost:8000 for direct backend access
const getApiUrl = () => {
// If explicitly set via env var, use it (for development)
if (process.env.NEXT_PUBLIC_API_URL) {
console.log('[API Client] Using explicit API URL:', process.env.NEXT_PUBLIC_API_URL);
return process.env.NEXT_PUBLIC_API_URL;
}
// For server-side rendering, always use relative URLs
if (typeof window === 'undefined') {
console.log('[API Client] SSR mode: using relative URLs');
return '';
}
// On localhost (dev mode), use direct backend URL
const hostname = window.location.hostname;
if (hostname === 'localhost' || hostname === '127.0.0.1') {
console.log('[API Client] Localhost dev mode: using http://localhost:8000');
return 'http://localhost:8000';
}
// In production (HF Space), use relative URLs (Next.js proxies to backend)
console.log('[API Client] Production mode: using relative URLs (proxied by Next.js)');
return '';
};
const API_URL = getApiUrl();
class ApiClient {
private client: AxiosInstance;
private token: string | null = null;
constructor() {
this.client = axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json',
},
});
// Add auth token to requests if available
this.client.interceptors.request.use((config) => {
if (this.token) {
config.headers.Authorization = `Bearer ${this.token}`;
}
return config;
});
// Load token from localStorage on client side
if (typeof window !== 'undefined') {
this.token = localStorage.getItem('hf_oauth_token');
}
}
setToken(token: string | null) {
this.token = token;
// Note: OAuth token is stored by auth.ts, not here
// We just keep it in memory for API calls
}
getToken(): string | null {
return this.token;
}
async getModels(): Promise<Model[]> {
const response = await this.client.get<Model[]>('/api/models');
return response.data;
}
async getLanguages(): Promise<{ languages: Language[] }> {
const response = await this.client.get<{ languages: Language[] }>('/api/languages');
return response.data;
}
async getAuthStatus(): Promise<AuthStatus> {
try {
const response = await this.client.get<AuthStatus>('/api/auth/status');
return response.data;
} catch (error) {
return {
authenticated: false,
message: 'Not authenticated',
};
}
}
// Stream-based code generation using EventSource (Server-Sent Events)
generateCodeStream(
request: CodeGenerationRequest,
onChunk: (content: string) => void,
onComplete: (code: string) => void,
onError: (error: string) => void
): () => void {
// Build the URL correctly whether we have a base URL or not
const baseUrl = API_URL || window.location.origin;
const url = new URL('/api/generate', baseUrl);
url.search = new URLSearchParams({
query: request.query,
language: request.language,
model_id: request.model_id,
provider: request.provider,
}).toString();
const eventSource = new EventSource(url.toString());
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
console.log('[SSE] Received event:', data.type, data.content?.substring(0, 30));
if (data.type === 'chunk' && data.content) {
onChunk(data.content);
} else if (data.type === 'complete' && data.code) {
console.log('[SSE] Generation complete, total code length:', data.code.length);
onComplete(data.code);
eventSource.close();
} else if (data.type === 'error') {
console.error('[SSE] Error:', data.message);
onError(data.message || 'Unknown error occurred');
eventSource.close();
}
} catch (error) {
console.error('Error parsing SSE data:', error);
}
};
eventSource.onerror = (error) => {
console.error('EventSource error:', error);
onError('Connection error occurred');
eventSource.close();
};
// Return cleanup function
return () => {
eventSource.close();
};
}
// Alternative: WebSocket-based generation
generateCodeWebSocket(
request: CodeGenerationRequest,
onChunk: (content: string) => void,
onComplete: (code: string) => void,
onError: (error: string) => void
): WebSocket {
// Build WebSocket URL correctly for both dev and production
const baseUrl = API_URL || window.location.origin;
const wsUrl = baseUrl.replace('http', 'ws') + '/ws/generate';
const ws = new WebSocket(wsUrl);
ws.onopen = () => {
ws.send(JSON.stringify(request));
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'chunk' && data.content) {
onChunk(data.content);
} else if (data.type === 'complete' && data.code) {
onComplete(data.code);
ws.close();
} else if (data.type === 'error') {
onError(data.message || 'Unknown error occurred');
ws.close();
}
} catch (error) {
console.error('Error parsing WebSocket data:', error);
}
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
onError('Connection error occurred');
};
return ws;
}
async deploy(request: DeploymentRequest): Promise<DeploymentResponse> {
const response = await this.client.post<DeploymentResponse>('/api/deploy', request);
return response.data;
}
async importProject(url: string, preferLocal: boolean = false): Promise<any> {
const response = await this.client.post('/api/import', { url, prefer_local: preferLocal });
return response.data;
}
async importSpace(username: string, spaceName: string): Promise<any> {
const response = await this.client.get(`/api/import/space/${username}/${spaceName}`);
return response.data;
}
async importModel(modelId: string, preferLocal: boolean = false): Promise<any> {
const response = await this.client.get(`/api/import/model/${modelId}`, {
params: { prefer_local: preferLocal }
});
return response.data;
}
async importGithub(owner: string, repo: string): Promise<any> {
const response = await this.client.get(`/api/import/github/${owner}/${repo}`);
return response.data;
}
logout() {
this.token = null;
}
}
// Export singleton instance
export const apiClient = new ApiClient();