Besjon Cifliku commited on
Commit ·
e29b232
1
Parent(s): 9f87ec0
feat: upadte logging in hf spaces
Browse files- Dockerfile +2 -1
- frontend/src/api.ts +3 -0
- frontend/src/components/LogViewer.tsx +19 -17
- server.py +10 -1
Dockerfile
CHANGED
|
@@ -40,12 +40,13 @@ COPY --chown=appuser *.py ./
|
|
| 40 |
COPY --chown=appuser --from=frontend-build /app/frontend/dist ./frontend/dist
|
| 41 |
|
| 42 |
# Data directories (HF cache, engine state, trained models)
|
| 43 |
-
RUN mkdir -p /data/huggingface /data/engine_state /data/trained_model \
|
| 44 |
&& chown -R appuser:appuser /app /data
|
| 45 |
|
| 46 |
ENV HF_HOME=/data/huggingface
|
| 47 |
ENV TRANSFORMERS_CACHE=/data/huggingface
|
| 48 |
ENV ENGINE_STATE_DIR=/data/engine_state
|
|
|
|
| 49 |
|
| 50 |
# Switch to non-root user
|
| 51 |
USER appuser
|
|
|
|
| 40 |
COPY --chown=appuser --from=frontend-build /app/frontend/dist ./frontend/dist
|
| 41 |
|
| 42 |
# Data directories (HF cache, engine state, trained models)
|
| 43 |
+
RUN mkdir -p /data/huggingface /data/engine_state /data/w2v_state /data/trained_model \
|
| 44 |
&& chown -R appuser:appuser /app /data
|
| 45 |
|
| 46 |
ENV HF_HOME=/data/huggingface
|
| 47 |
ENV TRANSFORMERS_CACHE=/data/huggingface
|
| 48 |
ENV ENGINE_STATE_DIR=/data/engine_state
|
| 49 |
+
ENV W2V_STATE_DIR=/data/w2v_state
|
| 50 |
|
| 51 |
# Switch to non-root user
|
| 52 |
USER appuser
|
frontend/src/api.ts
CHANGED
|
@@ -107,6 +107,9 @@ export const api = {
|
|
| 107 |
getStats: () =>
|
| 108 |
client.get<CorpusStats>("/stats").then(r => r.data),
|
| 109 |
|
|
|
|
|
|
|
|
|
|
| 110 |
getCorpusTexts: (maxDocs: number = 500) =>
|
| 111 |
client.get<{ documents: { doc_id: string; text: string }[]; count: number }>(`/corpus/texts?max_docs=${maxDocs}`).then(r => r.data),
|
| 112 |
|
|
|
|
| 107 |
getStats: () =>
|
| 108 |
client.get<CorpusStats>("/stats").then(r => r.data),
|
| 109 |
|
| 110 |
+
pollLogs: (cursor: number = 0) =>
|
| 111 |
+
client.get<{ lines: string[]; cursor: number }>(`/logs/poll?cursor=${cursor}`).then(r => r.data),
|
| 112 |
+
|
| 113 |
getCorpusTexts: (maxDocs: number = 500) =>
|
| 114 |
client.get<{ documents: { doc_id: string; text: string }[]; count: number }>(`/corpus/texts?max_docs=${maxDocs}`).then(r => r.data),
|
| 115 |
|
frontend/src/components/LogViewer.tsx
CHANGED
|
@@ -1,39 +1,41 @@
|
|
| 1 |
import { useState, useEffect, useRef } from "react";
|
|
|
|
| 2 |
|
| 3 |
interface Props {
|
| 4 |
-
/** Whether to actively
|
| 5 |
active: boolean;
|
| 6 |
}
|
| 7 |
|
| 8 |
export default function LogViewer({ active }: Props) {
|
| 9 |
const [lines, setLines] = useState<string[]>([]);
|
| 10 |
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
| 11 |
|
| 12 |
useEffect(() => {
|
| 13 |
if (!active) return;
|
| 14 |
|
| 15 |
setLines([]);
|
| 16 |
-
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
const
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
-
|
| 27 |
-
// SSE will auto-reconnect, no action needed
|
| 28 |
-
};
|
| 29 |
-
|
| 30 |
-
return () => {
|
| 31 |
-
evtSource.close();
|
| 32 |
-
};
|
| 33 |
}, [active]);
|
| 34 |
|
| 35 |
useEffect(() => {
|
| 36 |
-
// Auto-scroll to bottom
|
| 37 |
if (containerRef.current) {
|
| 38 |
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
| 39 |
}
|
|
|
|
| 1 |
import { useState, useEffect, useRef } from "react";
|
| 2 |
+
import { api } from "../api";
|
| 3 |
|
| 4 |
interface Props {
|
| 5 |
+
/** Whether to actively poll for logs */
|
| 6 |
active: boolean;
|
| 7 |
}
|
| 8 |
|
| 9 |
export default function LogViewer({ active }: Props) {
|
| 10 |
const [lines, setLines] = useState<string[]>([]);
|
| 11 |
const containerRef = useRef<HTMLDivElement>(null);
|
| 12 |
+
const cursorRef = useRef(0);
|
| 13 |
|
| 14 |
useEffect(() => {
|
| 15 |
if (!active) return;
|
| 16 |
|
| 17 |
setLines([]);
|
| 18 |
+
cursorRef.current = 0;
|
| 19 |
|
| 20 |
+
const interval = setInterval(async () => {
|
| 21 |
+
try {
|
| 22 |
+
const res = await api.pollLogs(cursorRef.current);
|
| 23 |
+
if (res.lines.length > 0) {
|
| 24 |
+
setLines((prev) => {
|
| 25 |
+
const next = [...prev, ...res.lines];
|
| 26 |
+
return next.length > 200 ? next.slice(-200) : next;
|
| 27 |
+
});
|
| 28 |
+
}
|
| 29 |
+
cursorRef.current = res.cursor;
|
| 30 |
+
} catch {
|
| 31 |
+
// ignore polling errors
|
| 32 |
+
}
|
| 33 |
+
}, 800);
|
| 34 |
|
| 35 |
+
return () => clearInterval(interval);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
}, [active]);
|
| 37 |
|
| 38 |
useEffect(() => {
|
|
|
|
| 39 |
if (containerRef.current) {
|
| 40 |
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
| 41 |
}
|
server.py
CHANGED
|
@@ -133,7 +133,9 @@ app = FastAPI(
|
|
| 133 |
|
| 134 |
app.add_middleware(
|
| 135 |
CORSMiddleware,
|
| 136 |
-
allow_origins=["http://localhost:5173", "http://localhost:3000"
|
|
|
|
|
|
|
| 137 |
allow_credentials=True,
|
| 138 |
allow_methods=["GET", "POST"],
|
| 139 |
allow_headers=["Content-Type", "Authorization"],
|
|
@@ -194,6 +196,13 @@ async def stream_logs():
|
|
| 194 |
)
|
| 195 |
|
| 196 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
# ------------------------------------------------------------------ #
|
| 198 |
# Request models (with input validation)
|
| 199 |
# ------------------------------------------------------------------ #
|
|
|
|
| 133 |
|
| 134 |
app.add_middleware(
|
| 135 |
CORSMiddleware,
|
| 136 |
+
allow_origins=["http://localhost:5173", "http://localhost:3000",
|
| 137 |
+
"https://huggingface.co", "https://*.hf.space"],
|
| 138 |
+
allow_origin_regex=r"https://.*\.hf\.space",
|
| 139 |
allow_credentials=True,
|
| 140 |
allow_methods=["GET", "POST"],
|
| 141 |
allow_headers=["Content-Type", "Authorization"],
|
|
|
|
| 196 |
)
|
| 197 |
|
| 198 |
|
| 199 |
+
@app.get("/api/logs/poll")
|
| 200 |
+
def poll_logs(cursor: int = Query(default=0, ge=0)):
|
| 201 |
+
"""Polling fallback for log streaming (works through HF Spaces proxy)."""
|
| 202 |
+
lines, new_cursor = log_buffer.get_new_lines(cursor)
|
| 203 |
+
return {"lines": lines, "cursor": new_cursor}
|
| 204 |
+
|
| 205 |
+
|
| 206 |
# ------------------------------------------------------------------ #
|
| 207 |
# Request models (with input validation)
|
| 208 |
# ------------------------------------------------------------------ #
|