From 1bafbf1eff4505764f89ab2110b9f9ca097bfd39 Mon Sep 17 00:00:00 2001 From: bruno Date: Sun, 19 Jul 2026 14:12:04 -0400 Subject: [PATCH] fix(library): tab Workspace affiche le contenu du workspace local actif MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nouvel endpoint /api/library/local-workspace?workspace_id=X interroge local_workspace_items et formate pour la Library - Nouvel endpoint /api/library/local-workspace-children/{id} pour l'expansion d'arborescence des items du workspace local - Frontend: dΓ©tecte workspaceId + !isGiteaActive β†’ appelle local-workspace - Frontend: toggleExpand route vers le bon endpoint selon source_type --- app/routers/library.py | 139 +++++++++++++++++++++++++++++++++++++ app/templates/library.html | 17 ++++- 2 files changed, 153 insertions(+), 3 deletions(-) diff --git a/app/routers/library.py b/app/routers/library.py index 6f3e000..86f82cb 100644 --- a/app/routers/library.py +++ b/app/routers/library.py @@ -255,6 +255,69 @@ async def library_private( return {"items": items} +@router.get("/local-workspace-children/{item_id:int}") +async def library_local_workspace_children(item_id: int, request: Request): + """Return children of a local workspace item for tree expansion.""" + uid = _get_user_id(request) + with get_conn() as conn: + # Get the item to find its workspace + item = conn.execute( + "SELECT workspace_id FROM local_workspace_items WHERE id=? AND deleted_at IS NULL", + [item_id], + ).fetchone() + if not item: + return {"items": []} + + rows = conn.execute( + "SELECT id, name, is_folder, parent_id, content_format, size, " + "COALESCE(updated_at, created_at) as updated_at " + "FROM local_workspace_items " + "WHERE parent_id = ? AND deleted_at IS NULL " + "ORDER BY is_folder DESC, name COLLATE NOCASE", + [item_id], + ).fetchall() + + items = [] + for r in rows: + name = r["name"] or "Untitled" + is_folder = bool(r["is_folder"]) + icon = "πŸ“" if is_folder else "πŸ“„" + fn = name.lower() + if not is_folder: + if fn.endswith(".pdf"): icon = "πŸ“•" + elif any(fn.endswith(e) for e in [".png",".jpg",".jpeg",".gif",".webp",".svg"]): icon = "πŸ–ΌοΈ" + elif any(fn.endswith(e) for e in [".py",".js",".ts",".go",".rs"]): icon = "πŸ“œ" + + with get_conn() as conn: + child_count = conn.execute( + "SELECT COUNT(*) FROM local_workspace_items WHERE parent_id=? AND deleted_at IS NULL", + [r["id"]], + ).fetchone()[0] + + items.append({ + "id": r["id"], + "title": name, + "icon": icon, + "is_folder": is_folder, + "source_type": "local-ws", + "source_label": "", + "workspace": "", + "workspace_name": "", + "author": "", + "author_initial": "?", + "updated_at": r["updated_at"] or "", + "visited_at": "", + "has_children": child_count > 0, + "children": [], + "url": f"/local-workspace?folder={r['id']}" if is_folder else f"/pages/{r['id']}", + "content_format": r["content_format"] or "file", + "favorited": False, + "size_display": _format_size(r["size"]) if r["size"] else "", + }) + + return {"items": items} + + @router.get("/children/{page_id:int}") async def library_children(page_id: int, request: Request): """Return child pages for a given parent page (for tree expansion in Library).""" @@ -295,6 +358,82 @@ async def library_repository( return {"items": items} +@router.get("/local-workspace") +async def library_local_workspace( + request: Request, + workspace_id: int = Query(default=0), +): + """Return local workspace items (files/folders) formatted for Library display.""" + from app.routers.dashboard import _get_active_workspace + uid = _get_user_id(request) + + # Get the active workspace + ws = _get_active_workspace(request, user_id=uid) + if not ws: + return {"items": []} + + ws_id = workspace_id or ws["id"] + + # Query local workspace tree + with get_conn() as conn: + rows = conn.execute( + "SELECT id, name, is_folder, parent_id, content_format, size, " + "COALESCE(updated_at, created_at) as updated_at " + "FROM local_workspace_items " + "WHERE workspace_id = ? AND deleted_at IS NULL " + "ORDER BY is_folder DESC, name COLLATE NOCASE", + [ws_id], + ).fetchall() + + items = [] + for r in rows: + name = r["name"] or "Untitled" + is_folder = bool(r["is_folder"]) + icon = "πŸ“" if is_folder else "πŸ“„" + fn = name.lower() + if not is_folder: + if fn.endswith(".pdf"): icon = "πŸ“•" + elif any(fn.endswith(e) for e in [".png",".jpg",".jpeg",".gif",".webp",".svg"]): icon = "πŸ–ΌοΈ" + elif any(fn.endswith(e) for e in [".py",".js",".ts",".go",".rs"]): icon = "πŸ“œ" + + # Check for children + child_count = conn.execute( + "SELECT COUNT(*) FROM local_workspace_items WHERE parent_id=? AND deleted_at IS NULL", + [r["id"]], + ).fetchone()[0] + + items.append({ + "id": r["id"], + "title": name, + "icon": icon, + "is_folder": is_folder, + "source_type": "local-ws", + "source_label": ws.get("name", "Workspace"), + "workspace": ws.get("name", ""), + "workspace_name": ws.get("name", ""), + "author": "", + "author_initial": "?", + "updated_at": r["updated_at"] or "", + "visited_at": "", + "has_children": child_count > 0, + "children": [], + "url": f"/local-workspace?folder={r['id']}" if is_folder else f"/pages/{r['id']}", + "content_format": r["content_format"] or "file", + "favorited": False, + "size_display": _format_size(r["size"]) if r["size"] else "", + }) + + return {"items": items} + + +def _format_size(size_bytes): + if not size_bytes: return "" + if size_bytes < 1024: return f"{size_bytes} B" + if size_bytes < 1048576: return f"{size_bytes/1024:.1f} KB" + if size_bytes < 1073741824: return f"{size_bytes/1048576:.1f} MB" + return f"{size_bytes/1073741824:.1f} GB" + + @router.get("/workspace") async def library_workspace( request: Request, diff --git a/app/templates/library.html b/app/templates/library.html index abafd50..11bbe2a 100644 --- a/app/templates/library.html +++ b/app/templates/library.html @@ -464,8 +464,14 @@ function libraryPage() { try { var url = '/api/library/' + this.tab; var sep = '?'; - if (this.workspaceId && (this.tab === 'recents')) { - url += sep + 'workspace_id=' + this.workspaceId; sep = '&'; + // Workspace tab: if local workspace is active, show local workspace items + if (this.tab === 'workspace' && this.workspaceId && !this.isGiteaActive) { + url = '/api/library/local-workspace' + sep + 'workspace_id=' + this.workspaceId; + sep = '&'; + } else { + if (this.workspaceId && (this.tab === 'recents')) { + url += sep + 'workspace_id=' + this.workspaceId; sep = '&'; + } } if (this.tab === 'repository' && this.isGiteaActive) { url += sep + 'gitea_owner=' + encodeURIComponent(this.giteaOwner) + '&gitea_repo=' + encodeURIComponent(this.giteaRepo); @@ -522,7 +528,12 @@ function libraryPage() { this.expanded = Object.assign({}, this.expanded); if (!item.children || !item.children.length) { try { - var r = await fetch('/api/library/children/' + item.id); + // Route to correct children endpoint based on source type + var childrenUrl = '/api/library/children/' + item.id; + if (item.source_type === 'local-ws') { + childrenUrl = '/api/library/local-workspace-children/' + item.id; + } + var r = await fetch(childrenUrl); var d = await r.json(); item.children = d.items || []; // Mark the tree for depth calculation