diff --git a/web/app.py b/web/app.py index 7f3c177..f037cdd 100644 --- a/web/app.py +++ b/web/app.py @@ -565,6 +565,62 @@ async def api_learn(case_number: str): return {"status": "final", "message": "לולאת למידה הופעלה — גרסה סופית נקלטה"} +# ── Local files API — research, drafts, proofread ── + + +@app.get("/api/cases/{case_number}/local-files") +async def api_local_files(case_number: str): + """List local files from case subdirectories (research, drafts, proofread).""" + case_dir = config.find_case_dir(case_number) + result = {} + for folder in ("research", "proofread"): + folder_path = case_dir / "documents" / folder + if folder_path.exists(): + files = [] + for f in sorted(folder_path.iterdir()): + if f.is_file() and not f.name.startswith("."): + stat = f.stat() + files.append({ + "filename": f.name, + "size": stat.st_size, + "modified_at": stat.st_mtime, + "folder": folder, + }) + if files: + result[folder] = files + # Drafts are at case level, not under documents + drafts_path = case_dir / "drafts" + if drafts_path.exists(): + files = [] + for f in sorted(drafts_path.iterdir()): + if f.is_file() and not f.name.startswith("."): + stat = f.stat() + files.append({ + "filename": f.name, + "size": stat.st_size, + "modified_at": stat.st_mtime, + "folder": "drafts", + }) + if files: + result["drafts"] = files + return result + + +@app.get("/api/cases/{case_number}/local-files/{folder}/{filename}") +async def api_read_local_file(case_number: str, folder: str, filename: str): + """Read contents of a local case file.""" + if folder not in ("research", "proofread", "drafts"): + raise HTTPException(400, "Invalid folder") + case_dir = config.find_case_dir(case_number) + if folder == "drafts": + path = case_dir / "drafts" / filename + else: + path = case_dir / "documents" / folder / filename + if not path.exists() or not path.is_file(): + raise HTTPException(404, "קובץ לא נמצא") + return FileResponse(path, media_type="text/plain; charset=utf-8", filename=filename) + + # ── Exports API — drafts, versions, download, upload, mark-final ── diff --git a/web/static/index.html b/web/static/index.html index a945b81..a17b5e4 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -154,6 +154,14 @@ header nav a:hover, header nav a.active { color: #fff; background: rgba(255,255, .doc-item .doc-icon { color: #999; } .doc-item .doc-name { flex: 1; } .doc-item .doc-status { font-size: 0.75em; color: #999; } +.doc-status.completed { color: #27ae60; font-weight: 700; font-size: 1em; } +.doc-status.processing { display: inline-block; width: 14px; height: 14px; border: 2px solid #e94560; border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; } +.doc-status.pending { color: #ccc; } +.doc-status.failed { color: #e94560; font-weight: 700; } +.btn-retry { background: none; border: 1px solid #e94560; color: #e94560; border-radius: 4px; padding: 2px 8px; font-size: 0.75em; cursor: pointer; margin-right: 6px; } +.btn-retry:hover { background: #e94560; color: #fff; } +.processing-badge { display: inline-flex; align-items: center; gap: 4px; color: #e94560; font-size: 0.78em; font-weight: 500; } +.mini-spinner { display: inline-block; width: 10px; height: 10px; border: 1.5px solid #e94560; border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; } /* Upload zone (reusable) */ .upload-zone { @@ -217,6 +225,19 @@ header nav a:hover, header nav a.active { color: #fff; background: rgba(255,255, .toast.error { background: #e94560; } .toast.success { background: #27ae60; } +/* ── Local files (research, drafts, proofread) ───────── */ +.local-file-group { margin-bottom: 12px; } +.local-file-group-header { font-size: 0.82em; font-weight: 600; color: #666; margin-bottom: 6px; padding-bottom: 4px; border-bottom: 1px solid #eee; } +.local-file-item { + display: flex; align-items: center; gap: 10px; padding: 8px 12px; + border: 1px solid #eee; border-radius: 6px; margin-bottom: 4px; background: #fafafa; + cursor: pointer; transition: background 0.15s; +} +.local-file-item:hover { background: #f0f0f0; } +.local-file-item .lf-icon { flex-shrink: 0; font-size: 1.1em; } +.local-file-item .lf-name { flex: 1; font-size: 0.85em; font-weight: 500; word-break: break-all; } +.local-file-item .lf-meta { font-size: 0.72em; color: #999; flex-shrink: 0; } + /* ── Exports / Drafts ─────────────────────────────────── */ .export-item { display: flex; align-items: center; gap: 12px; padding: 10px 14px; @@ -448,6 +469,14 @@ header nav a:hover, header nav a.active { color: #fff; background: rgba(255,255, + +