Spaces:
Running
Running
Fix empty Language and AI Model dropdowns
Browse files- Update CORS configuration to allow HuggingFace Space domain and use regex for *.hf.space
- Improve API URL logic in frontend to correctly detect production vs dev environment
- Fix EventSource and WebSocket URL construction to work in both environments
- Allow requests from localhost:7860 for Docker Space internal communication
- backend_api.py +11 -2
- frontend/src/lib/api.ts +37 -15
backend_api.py
CHANGED
|
@@ -34,13 +34,22 @@ LANGUAGE_CHOICES = ["html", "gradio", "transformers.js", "streamlit", "comfyui",
|
|
| 34 |
|
| 35 |
app = FastAPI(title="AnyCoder API", version="1.0.0")
|
| 36 |
|
| 37 |
-
# Configure CORS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
app.add_middleware(
|
| 39 |
CORSMiddleware,
|
| 40 |
-
allow_origins=["
|
| 41 |
allow_credentials=True,
|
| 42 |
allow_methods=["*"],
|
| 43 |
allow_headers=["*"],
|
|
|
|
| 44 |
)
|
| 45 |
|
| 46 |
# OAuth configuration
|
|
|
|
| 34 |
|
| 35 |
app = FastAPI(title="AnyCoder API", version="1.0.0")
|
| 36 |
|
| 37 |
+
# Configure CORS - allow all origins in production, specific in dev
|
| 38 |
+
# In Docker Space, requests come from the same domain via Next.js proxy
|
| 39 |
+
ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS", "*").split(",") if os.getenv("ALLOWED_ORIGINS") else [
|
| 40 |
+
"http://localhost:3000",
|
| 41 |
+
"http://localhost:3001",
|
| 42 |
+
"http://localhost:7860",
|
| 43 |
+
f"https://{SPACE_HOST}" if SPACE_HOST and not SPACE_HOST.startswith("localhost") else "http://localhost:7860"
|
| 44 |
+
]
|
| 45 |
+
|
| 46 |
app.add_middleware(
|
| 47 |
CORSMiddleware,
|
| 48 |
+
allow_origins=ALLOWED_ORIGINS if ALLOWED_ORIGINS != ["*"] else ["*"],
|
| 49 |
allow_credentials=True,
|
| 50 |
allow_methods=["*"],
|
| 51 |
allow_headers=["*"],
|
| 52 |
+
allow_origin_regex=r"https://.*\.hf\.space" if os.getenv("SPACE_HOST") else None,
|
| 53 |
)
|
| 54 |
|
| 55 |
# OAuth configuration
|
frontend/src/lib/api.ts
CHANGED
|
@@ -11,11 +11,29 @@ import type {
|
|
| 11 |
} from '@/types';
|
| 12 |
|
| 13 |
// Use relative URLs in production (Next.js rewrites will proxy to backend)
|
| 14 |
-
// In local dev
|
| 15 |
-
const
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
class ApiClient {
|
| 21 |
private client: AxiosInstance;
|
|
@@ -82,15 +100,17 @@ class ApiClient {
|
|
| 82 |
onComplete: (code: string) => void,
|
| 83 |
onError: (error: string) => void
|
| 84 |
): () => void {
|
| 85 |
-
|
| 86 |
-
const
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
);
|
|
|
|
|
|
|
| 94 |
|
| 95 |
eventSource.onmessage = (event) => {
|
| 96 |
try {
|
|
@@ -129,7 +149,9 @@ class ApiClient {
|
|
| 129 |
onComplete: (code: string) => void,
|
| 130 |
onError: (error: string) => void
|
| 131 |
): WebSocket {
|
| 132 |
-
|
|
|
|
|
|
|
| 133 |
const ws = new WebSocket(wsUrl);
|
| 134 |
|
| 135 |
ws.onopen = () => {
|
|
|
|
| 11 |
} from '@/types';
|
| 12 |
|
| 13 |
// Use relative URLs in production (Next.js rewrites will proxy to backend)
|
| 14 |
+
// In local dev, use localhost:8000 for direct backend access
|
| 15 |
+
const getApiUrl = () => {
|
| 16 |
+
// If explicitly set via env var, use it
|
| 17 |
+
if (process.env.NEXT_PUBLIC_API_URL) {
|
| 18 |
+
return process.env.NEXT_PUBLIC_API_URL;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
// For server-side rendering, always use relative URLs
|
| 22 |
+
if (typeof window === 'undefined') {
|
| 23 |
+
return '';
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
// On localhost (dev mode), use direct backend URL
|
| 27 |
+
const hostname = window.location.hostname;
|
| 28 |
+
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
| 29 |
+
return 'http://localhost:8000';
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
// In production (HF Space), use relative URLs (Next.js proxies to backend)
|
| 33 |
+
return '';
|
| 34 |
+
};
|
| 35 |
+
|
| 36 |
+
const API_URL = getApiUrl();
|
| 37 |
|
| 38 |
class ApiClient {
|
| 39 |
private client: AxiosInstance;
|
|
|
|
| 100 |
onComplete: (code: string) => void,
|
| 101 |
onError: (error: string) => void
|
| 102 |
): () => void {
|
| 103 |
+
// Build the URL correctly whether we have a base URL or not
|
| 104 |
+
const baseUrl = API_URL || window.location.origin;
|
| 105 |
+
const url = new URL('/api/generate', baseUrl);
|
| 106 |
+
url.search = new URLSearchParams({
|
| 107 |
+
query: request.query,
|
| 108 |
+
language: request.language,
|
| 109 |
+
model_id: request.model_id,
|
| 110 |
+
provider: request.provider,
|
| 111 |
+
}).toString();
|
| 112 |
+
|
| 113 |
+
const eventSource = new EventSource(url.toString());
|
| 114 |
|
| 115 |
eventSource.onmessage = (event) => {
|
| 116 |
try {
|
|
|
|
| 149 |
onComplete: (code: string) => void,
|
| 150 |
onError: (error: string) => void
|
| 151 |
): WebSocket {
|
| 152 |
+
// Build WebSocket URL correctly for both dev and production
|
| 153 |
+
const baseUrl = API_URL || window.location.origin;
|
| 154 |
+
const wsUrl = baseUrl.replace('http', 'ws') + '/ws/generate';
|
| 155 |
const ws = new WebSocket(wsUrl);
|
| 156 |
|
| 157 |
ws.onopen = () => {
|