Spaces:
Sleeping
Sleeping
Commit ·
cc036eb
1
Parent(s): 0f5ca68
Fix: use same-origin proxy for HF inference (avoid CORS)
Browse files- .gitattributes +1 -0
- Dockerfile +21 -6
- entrypoint.sh +10 -0
- index.html +2 -0
- scripts/debug-space.js +76 -0
- src/components/TinyLlamaConverter.jsx +11 -7
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.sh text eol=lf
|
Dockerfile
CHANGED
|
@@ -32,21 +32,36 @@ RUN npm run build
|
|
| 32 |
# Stage 2: Serve the application with Nginx
|
| 33 |
FROM nginx:alpine
|
| 34 |
|
|
|
|
|
|
|
| 35 |
COPY --from=builder /app/dist /usr/share/nginx/html
|
|
|
|
| 36 |
|
| 37 |
-
# Copy custom nginx config if needed (optional but good practice for SPAs)
|
| 38 |
-
# Creating a simple config inline to handle client-side routing fallback
|
| 39 |
RUN echo 'server { \
|
| 40 |
listen 7860; \
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
location / { \
|
| 42 |
root /usr/share/nginx/html; \
|
| 43 |
index index.html index.htm; \
|
| 44 |
try_files $uri $uri/ /index.html; \
|
| 45 |
} \
|
| 46 |
-
}' > /etc/nginx/conf.d/default.conf
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
-
# Expose port 7860 for Hugging Face Spaces
|
| 49 |
EXPOSE 7860
|
| 50 |
|
| 51 |
-
|
| 52 |
-
CMD ["nginx", "-g", "daemon off;"]
|
|
|
|
| 32 |
# Stage 2: Serve the application with Nginx
|
| 33 |
FROM nginx:alpine
|
| 34 |
|
| 35 |
+
RUN adduser -D -u 1000 appuser
|
| 36 |
+
|
| 37 |
COPY --from=builder /app/dist /usr/share/nginx/html
|
| 38 |
+
COPY entrypoint.sh /entrypoint.sh
|
| 39 |
|
|
|
|
|
|
|
| 40 |
RUN echo 'server { \
|
| 41 |
listen 7860; \
|
| 42 |
+
location /api/hf-inference/ { \
|
| 43 |
+
rewrite ^/api/hf-inference/(.*)$ /$1 break; \
|
| 44 |
+
proxy_pass https://api-inference.huggingface.co; \
|
| 45 |
+
proxy_http_version 1.1; \
|
| 46 |
+
proxy_set_header Host api-inference.huggingface.co; \
|
| 47 |
+
proxy_set_header Authorization "Bearer __VITE_HF_TOKEN__"; \
|
| 48 |
+
proxy_pass_request_headers on; \
|
| 49 |
+
proxy_buffering off; \
|
| 50 |
+
} \
|
| 51 |
location / { \
|
| 52 |
root /usr/share/nginx/html; \
|
| 53 |
index index.html index.htm; \
|
| 54 |
try_files $uri $uri/ /index.html; \
|
| 55 |
} \
|
| 56 |
+
}' > /etc/nginx/conf.d/default.conf.tpl
|
| 57 |
+
RUN sed 's|__VITE_HF_TOKEN__||g' /etc/nginx/conf.d/default.conf.tpl > /etc/nginx/conf.d/default.conf
|
| 58 |
+
|
| 59 |
+
RUN chmod +x /entrypoint.sh && \
|
| 60 |
+
chown -R appuser:appuser /usr/share/nginx/html /var/cache/nginx /var/log/nginx /etc/nginx/conf.d && \
|
| 61 |
+
touch /var/run/nginx.pid && chown appuser:appuser /var/run/nginx.pid
|
| 62 |
+
|
| 63 |
+
USER appuser
|
| 64 |
|
|
|
|
| 65 |
EXPOSE 7860
|
| 66 |
|
| 67 |
+
ENTRYPOINT ["/entrypoint.sh"]
|
|
|
entrypoint.sh
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/sh
|
| 2 |
+
# Inject runtime env and nginx proxy config (avoids CORS: browser hits same origin, nginx forwards to HF with token)
|
| 3 |
+
TOKEN="${VITE_HF_TOKEN:-}"
|
| 4 |
+
ESCAPED=$(printf '%s' "$TOKEN" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\$/\\$/g')
|
| 5 |
+
printf 'window.__HF_ENV={"VITE_HF_TOKEN":"%s"};\n' "$ESCAPED" > /usr/share/nginx/html/config.js
|
| 6 |
+
|
| 7 |
+
# Substitute token into nginx config (proxy avoids CORS)
|
| 8 |
+
sed "s|__VITE_HF_TOKEN__|$TOKEN|g" /etc/nginx/conf.d/default.conf.tpl > /etc/nginx/conf.d/default.conf
|
| 9 |
+
|
| 10 |
+
exec nginx -g "daemon off;"
|
index.html
CHANGED
|
@@ -7,6 +7,8 @@
|
|
| 7 |
</head>
|
| 8 |
<body>
|
| 9 |
<div id="root"></div>
|
|
|
|
|
|
|
| 10 |
<script type="module" src="/src/main.jsx"></script>
|
| 11 |
</body>
|
| 12 |
</html>
|
|
|
|
| 7 |
</head>
|
| 8 |
<body>
|
| 9 |
<div id="root"></div>
|
| 10 |
+
<script>window.__HF_ENV=window.__HF_ENV||{};</script>
|
| 11 |
+
<script src="/config.js"></script>
|
| 12 |
<script type="module" src="/src/main.jsx"></script>
|
| 13 |
</body>
|
| 14 |
</html>
|
scripts/debug-space.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Playwright script: open HF Space, trigger TinyLlama, capture console/network errors.
|
| 3 |
+
* Run: node scripts/debug-space.js
|
| 4 |
+
* Or with auth URL: SPACE_URL="https://user:TOKEN@huggingface.co/spaces/OnyxMunk/text-transformer" node scripts/debug-space.js
|
| 5 |
+
*/
|
| 6 |
+
const { chromium } = require('playwright');
|
| 7 |
+
|
| 8 |
+
const SPACE_URL = process.env.SPACE_URL || 'https://huggingface.co/spaces/OnyxMunk/text-transformer';
|
| 9 |
+
|
| 10 |
+
async function main() {
|
| 11 |
+
const browser = await chromium.launch({ headless: false });
|
| 12 |
+
const context = await browser.newContext();
|
| 13 |
+
const consoleLogs = [];
|
| 14 |
+
const requestFailures = [];
|
| 15 |
+
|
| 16 |
+
context.on('console', (msg) => {
|
| 17 |
+
const text = msg.text();
|
| 18 |
+
const type = msg.type();
|
| 19 |
+
consoleLogs.push({ type, text });
|
| 20 |
+
if (type === 'error' || text.toLowerCase().includes('failed') || text.toLowerCase().includes('cors')) {
|
| 21 |
+
console.error(`[CONSOLE ${type}]`, text);
|
| 22 |
+
}
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
context.on('requestfailed', (request) => {
|
| 26 |
+
const failure = { url: request.url(), failure: request.failure()?.errorText };
|
| 27 |
+
requestFailures.push(failure);
|
| 28 |
+
console.error('[REQUEST FAILED]', failure.url, failure.failure);
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
const page = await context.newPage();
|
| 32 |
+
await page.goto(SPACE_URL, { waitUntil: 'domcontentloaded', timeout: 60000 }).catch((e) => {
|
| 33 |
+
console.error('Navigate error:', e.message);
|
| 34 |
+
});
|
| 35 |
+
|
| 36 |
+
await page.waitForTimeout(5000);
|
| 37 |
+
|
| 38 |
+
const snapshot = await page.content();
|
| 39 |
+
if (snapshot.includes('Failed to connect') || snapshot.includes('CORS') || snapshot.includes('error')) {
|
| 40 |
+
const match = snapshot.match(/>([^<]*(?:Failed to connect|CORS|error)[^<]*)</);
|
| 41 |
+
if (match) console.error('Page contains error text:', match[1].trim().slice(0, 200));
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
const tinyLlamaTab = page.locator('button, [role="tab"], a').filter({ hasText: /TinyLlama|Tiny Llama/i }).first();
|
| 45 |
+
if (await tinyLlamaTab.count() > 0) {
|
| 46 |
+
await tinyLlamaTab.click();
|
| 47 |
+
await page.waitForTimeout(1500);
|
| 48 |
+
const promptInput = page.locator('textarea[placeholder*="TinyLlama"], textarea[id="llama-input"], textarea').first();
|
| 49 |
+
if (await promptInput.count() > 0) {
|
| 50 |
+
await promptInput.fill('Hello');
|
| 51 |
+
await page.waitForTimeout(500);
|
| 52 |
+
const generateBtn = page.locator('button[title="Generate Response"], button').filter({ hasText: /Generate|⚡/ }).first();
|
| 53 |
+
if (await generateBtn.count() > 0) {
|
| 54 |
+
await generateBtn.click();
|
| 55 |
+
await page.waitForTimeout(8000);
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
const errorEl = page.locator('text=/Failed to connect|CORS|error/i').first();
|
| 61 |
+
if (await errorEl.count() > 0) {
|
| 62 |
+
console.error('Visible error on page:', await errorEl.textContent());
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
console.log('\n--- Console messages (errors/warnings) ---');
|
| 66 |
+
consoleLogs.filter((m) => m.type === 'error' || m.type === 'warning').forEach((m) => console.log(m.type, m.text));
|
| 67 |
+
console.log('\n--- Failed network requests ---');
|
| 68 |
+
requestFailures.forEach((f) => console.log(f.url, f.failure));
|
| 69 |
+
|
| 70 |
+
await browser.close();
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
main().catch((e) => {
|
| 74 |
+
console.error(e);
|
| 75 |
+
process.exit(1);
|
| 76 |
+
});
|
src/components/TinyLlamaConverter.jsx
CHANGED
|
@@ -12,7 +12,9 @@ function getTinyLlamaConfig() {
|
|
| 12 |
if (typeof window === 'undefined') return { provider: 'ollama', baseUrl: OLLAMA_BASE };
|
| 13 |
const host = window.location.hostname;
|
| 14 |
if (host === 'localhost' || host === '127.0.0.1') return { provider: 'ollama', baseUrl: OLLAMA_BASE };
|
| 15 |
-
if (host.includes('huggingface.co') || host.includes('hf.space'))
|
|
|
|
|
|
|
| 16 |
if (host.includes('vercel.app')) {
|
| 17 |
const vercelUrl = env.VITE_TINYLLAMA_VERCEL_URL?.trim();
|
| 18 |
return { provider: 'ollama', baseUrl: vercelUrl || OLLAMA_BASE };
|
|
@@ -39,12 +41,10 @@ const TinyLlamaConverter = ({ sharedInput, setSharedInput }) => {
|
|
| 39 |
|
| 40 |
try {
|
| 41 |
if (config.provider === 'hf') {
|
| 42 |
-
const url = `
|
| 43 |
-
const headers = { 'Content-Type': 'application/json' };
|
| 44 |
-
if (config.token) headers['Authorization'] = `Bearer ${config.token}`;
|
| 45 |
const res = await fetch(url, {
|
| 46 |
method: 'POST',
|
| 47 |
-
headers,
|
| 48 |
body: JSON.stringify({
|
| 49 |
inputs: sharedInput.trim(),
|
| 50 |
parameters: { max_new_tokens: 512, return_full_text: false },
|
|
@@ -52,7 +52,9 @@ const TinyLlamaConverter = ({ sharedInput, setSharedInput }) => {
|
|
| 52 |
});
|
| 53 |
if (!res.ok) {
|
| 54 |
const errBody = await res.text();
|
| 55 |
-
throw new Error(res.status === 401
|
|
|
|
|
|
|
| 56 |
}
|
| 57 |
const data = await res.json();
|
| 58 |
const text = Array.isArray(data) ? data[0]?.generated_text : data?.generated_text;
|
|
@@ -82,7 +84,9 @@ const TinyLlamaConverter = ({ sharedInput, setSharedInput }) => {
|
|
| 82 |
if (err.message.includes("Failed to fetch")) {
|
| 83 |
const msg = config.provider === 'ollama' && config.baseUrl === OLLAMA_BASE
|
| 84 |
? "Failed to connect to Ollama. Ensure it's running on port 11434 and CORS is allowed (OLLAMA_ORIGINS=\"*\")."
|
| 85 |
-
:
|
|
|
|
|
|
|
| 86 |
setError(msg);
|
| 87 |
} else {
|
| 88 |
setError(err.message);
|
|
|
|
| 12 |
if (typeof window === 'undefined') return { provider: 'ollama', baseUrl: OLLAMA_BASE };
|
| 13 |
const host = window.location.hostname;
|
| 14 |
if (host === 'localhost' || host === '127.0.0.1') return { provider: 'ollama', baseUrl: OLLAMA_BASE };
|
| 15 |
+
if (host.includes('huggingface.co') || host.includes('hf.space')) {
|
| 16 |
+
return { provider: 'hf', baseUrl: '/api/hf-inference', token: null };
|
| 17 |
+
}
|
| 18 |
if (host.includes('vercel.app')) {
|
| 19 |
const vercelUrl = env.VITE_TINYLLAMA_VERCEL_URL?.trim();
|
| 20 |
return { provider: 'ollama', baseUrl: vercelUrl || OLLAMA_BASE };
|
|
|
|
| 41 |
|
| 42 |
try {
|
| 43 |
if (config.provider === 'hf') {
|
| 44 |
+
const url = `/api/hf-inference/models/${HF_MODEL}`;
|
|
|
|
|
|
|
| 45 |
const res = await fetch(url, {
|
| 46 |
method: 'POST',
|
| 47 |
+
headers: { 'Content-Type': 'application/json' },
|
| 48 |
body: JSON.stringify({
|
| 49 |
inputs: sharedInput.trim(),
|
| 50 |
parameters: { max_new_tokens: 512, return_full_text: false },
|
|
|
|
| 52 |
});
|
| 53 |
if (!res.ok) {
|
| 54 |
const errBody = await res.text();
|
| 55 |
+
throw new Error(res.status === 401
|
| 56 |
+
? 'Hugging Face token missing or invalid. Add VITE_HF_TOKEN in Space Settings → Variables and secrets, then restart.'
|
| 57 |
+
: errBody || res.statusText);
|
| 58 |
}
|
| 59 |
const data = await res.json();
|
| 60 |
const text = Array.isArray(data) ? data[0]?.generated_text : data?.generated_text;
|
|
|
|
| 84 |
if (err.message.includes("Failed to fetch")) {
|
| 85 |
const msg = config.provider === 'ollama' && config.baseUrl === OLLAMA_BASE
|
| 86 |
? "Failed to connect to Ollama. Ensure it's running on port 11434 and CORS is allowed (OLLAMA_ORIGINS=\"*\")."
|
| 87 |
+
: config.provider === 'hf'
|
| 88 |
+
? "CORS or network error. Ensure the latest code is deployed (with /api/hf-inference proxy) and VITE_HF_TOKEN is set in Space Settings → Secrets, then restart."
|
| 89 |
+
: `Failed to connect to ${config.baseUrl}. Check network and CORS.`;
|
| 90 |
setError(msg);
|
| 91 |
} else {
|
| 92 |
setError(err.message);
|