Besjon Cifliku commited on
Commit
e29b232
·
1 Parent(s): 9f87ec0

feat: upadte logging in hf spaces

Browse files
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 stream logs */
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
- const evtSource = new EventSource("/api/logs/stream");
17
 
18
- evtSource.onmessage = (event) => {
19
- setLines((prev) => {
20
- const next = [...prev, event.data];
21
- // Keep last 200 lines
22
- return next.length > 200 ? next.slice(-200) : next;
23
- });
24
- };
 
 
 
 
 
 
 
25
 
26
- evtSource.onerror = () => {
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
  # ------------------------------------------------------------------ #